315 lines
14 KiB
Bash
Executable File
315 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# =============================================================================
|
||
# 03-start-infra.sh — 启动 Docker 基础设施(Redis / Kafka / Etcd / MinIO / LiveKit)
|
||
#
|
||
# 数据目录: .local-dev/docker-data/<svc>/
|
||
# 容器日志: .local-dev/docker-logs/<svc>/<svc>-YYYYMMDD.log(每日一文件)
|
||
# 脚本日志: .local-dev/script-logs/03-start-infra-<ts>.log
|
||
#
|
||
# 后续步骤:04-build.sh(编译后端服务)
|
||
# =============================================================================
|
||
set -euo pipefail
|
||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
||
load_env
|
||
init_dirs
|
||
init_script_log # ← 脚本执行日志
|
||
require_docker_running
|
||
|
||
# Docker 日志驱动公共参数(JSON 文件,最多 5 个 50MB 轮转)
|
||
LOG_OPTS=(
|
||
--log-driver json-file
|
||
--log-opt max-size=50m
|
||
--log-opt max-file=5
|
||
)
|
||
|
||
header "步骤 3 / 5 — 启动 Docker 基础设施"
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Redis
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
step "Redis"
|
||
|
||
if docker ps --format '{{.Names}}' | grep -q '^dev-redis$'; then
|
||
success "Redis 已在运行 (container=dev-redis) :${REDIS_PORT}"
|
||
elif docker ps -a --format '{{.Names}}' | grep -q '^dev-redis$'; then
|
||
info "重新启动已有容器 dev-redis..."
|
||
docker start dev-redis > /dev/null
|
||
success "Redis 已启动 :${REDIS_PORT}"
|
||
else
|
||
info "创建并启动 Redis 容器..."
|
||
docker run -d \
|
||
--name dev-redis \
|
||
--restart unless-stopped \
|
||
-p "${REDIS_PORT}:6379" \
|
||
-v "${DATA_DIR}/redis:/data" \
|
||
"${LOG_OPTS[@]}" \
|
||
redis:7-alpine \
|
||
redis-server --requirepass "${REDIS_PASSWORD:-openIM123}" --appendonly yes \
|
||
> /dev/null
|
||
success "Redis 容器已创建并启动 :${REDIS_PORT} (密码: ${REDIS_PASSWORD:-openIM123})"
|
||
fi
|
||
|
||
sleep 1
|
||
if docker exec dev-redis redis-cli -a "${REDIS_PASSWORD:-openIM123}" ping 2>/dev/null | grep -q PONG; then
|
||
success "Redis 连通性: PONG ✓"
|
||
else
|
||
warn "Redis 未响应 PING,请查看日志: ./deploy-test/logs.sh redis"
|
||
fi
|
||
start_docker_logger "dev-redis"
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Kafka(KRaft 模式)
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
step "Kafka (KRaft)"
|
||
|
||
if docker ps --format '{{.Names}}' | grep -q '^dev-kafka$'; then
|
||
success "Kafka 已在运行 (container=dev-kafka) 本机 :${KAFKA_PORT} 外网 ${DEPLOY_TEST_IP}:${KAFKA_EXTERNAL_PORT:-9094}"
|
||
elif docker ps -a --format '{{.Names}}' | grep -q '^dev-kafka$'; then
|
||
info "重新启动已有容器 dev-kafka..."
|
||
docker start dev-kafka > /dev/null
|
||
else
|
||
info "创建并启动 Kafka 容器(首次拉取镜像可能较慢)..."
|
||
KAFKA_CLUSTER_ID="MkU3OEVBNTcwNTJENDM2Qk"
|
||
KAFKA_EXTERNAL_PORT="${KAFKA_EXTERNAL_PORT:-9094}"
|
||
|
||
# bitnamilegacy/kafka 容器内以 uid=1001 运行,宿主目录需提前授权
|
||
mkdir -p "${DATA_DIR}/kafka"
|
||
chown -R 1001:1001 "${DATA_DIR}/kafka" 2>/dev/null || \
|
||
chmod -R 777 "${DATA_DIR}/kafka" # 无 chown 权限时退回 777
|
||
|
||
# 双 listener:INTERNAL 供本机服务(与 kafka.yml 127.0.0.1 一致);EXTERNAL 供公网 bootstrap(advertise DEPLOY_TEST_IP)
|
||
docker run -d \
|
||
--name dev-kafka \
|
||
--restart unless-stopped \
|
||
-p "${KAFKA_PORT}:9092" \
|
||
-p "${KAFKA_EXTERNAL_PORT}:9094" \
|
||
-v "${DATA_DIR}/kafka:/bitnami/kafka" \
|
||
-e KAFKA_CFG_NODE_ID=0 \
|
||
-e KAFKA_CFG_PROCESS_ROLES=controller,broker \
|
||
-e KAFKA_CFG_LISTENERS="INTERNAL://:9092,EXTERNAL://:9094,CONTROLLER://:9093" \
|
||
-e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP="CONTROLLER:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT" \
|
||
-e KAFKA_CFG_CONTROLLER_QUORUM_VOTERS="0@localhost:9093" \
|
||
-e KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER \
|
||
-e KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INTERNAL \
|
||
-e KAFKA_CFG_ADVERTISED_LISTENERS="INTERNAL://127.0.0.1:${KAFKA_PORT},EXTERNAL://${DEPLOY_TEST_IP}:${KAFKA_EXTERNAL_PORT}" \
|
||
-e KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true \
|
||
-e KAFKA_KRAFT_CLUSTER_ID="$KAFKA_CLUSTER_ID" \
|
||
"${LOG_OPTS[@]}" \
|
||
bitnamilegacy/kafka:3.5.1 \
|
||
> /dev/null
|
||
info " 本机 bootstrap: 127.0.0.1:${KAFKA_PORT}(INTERNAL)"
|
||
info " 外网 bootstrap: ${DEPLOY_TEST_IP}:${KAFKA_EXTERNAL_PORT}(EXTERNAL,PLAINTEXT;请在安全组放行 TCP ${KAFKA_EXTERNAL_PORT})"
|
||
fi
|
||
start_docker_logger "dev-kafka"
|
||
|
||
# 等待 Kafka broker 真正就绪(轮询,最长 90s)
|
||
info "等待 Kafka broker 就绪..."
|
||
_kafka_ready=0
|
||
for _i in $(seq 1 45); do
|
||
printf " 第 %d 次探测 (%ds)...\r" "$_i" "$(( _i * 2 ))"
|
||
# 先快速检测端口是否开放(避免 kafka-topics.sh 长时间挂起)
|
||
if docker exec dev-kafka bash -c "timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/9092'" &>/dev/null; then
|
||
# 端口开放后再验证 broker 是否真正接受请求
|
||
if docker exec dev-kafka timeout 5 kafka-topics.sh \
|
||
--bootstrap-server localhost:9092 \
|
||
--list &>/dev/null; then
|
||
_kafka_ready=1
|
||
break
|
||
fi
|
||
fi
|
||
sleep 2
|
||
done
|
||
echo "" # 清除 \r 留下的行
|
||
if [[ $_kafka_ready -eq 1 ]]; then
|
||
success "Kafka 已就绪 本机 127.0.0.1:${KAFKA_PORT} 外网 ${DEPLOY_TEST_IP}:${KAFKA_EXTERNAL_PORT:-9094}"
|
||
else
|
||
error "Kafka 90s 内未就绪,请检查日志: ./deploy-test/logs.sh kafka"
|
||
exit 1
|
||
fi
|
||
|
||
# 初始化必要 Topics
|
||
step "Kafka Topics 初始化"
|
||
TOPICS=(toRedis toMongo toPush toOfflinePush)
|
||
# 先获取已存在的 topic 列表,避免逐个查询
|
||
_existing_topics=$(docker exec dev-kafka kafka-topics.sh \
|
||
--bootstrap-server localhost:9092 --list 2>/dev/null || true)
|
||
|
||
for topic in "${TOPICS[@]}"; do
|
||
if echo "$_existing_topics" | grep -q "^${topic}$"; then
|
||
info " topic 已存在: $topic,跳过"
|
||
else
|
||
docker exec dev-kafka kafka-topics.sh \
|
||
--create \
|
||
--if-not-exists \
|
||
--topic "$topic" \
|
||
--bootstrap-server localhost:9092 \
|
||
--partitions 8 \
|
||
--replication-factor 1 \
|
||
2>/dev/null \
|
||
&& success " ✓ 创建 topic: $topic" \
|
||
|| warn " ✗ 创建失败: $topic"
|
||
fi
|
||
done
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# Etcd
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
step "Etcd"
|
||
|
||
if docker ps --format '{{.Names}}' | grep -q '^dev-etcd$'; then
|
||
success "Etcd 已在运行 (container=dev-etcd) :${ETCD_PORT}"
|
||
elif docker ps -a --format '{{.Names}}' | grep -q '^dev-etcd$'; then
|
||
info "重新启动已有容器 dev-etcd..."
|
||
docker start dev-etcd > /dev/null
|
||
sleep 2
|
||
success "Etcd 已启动 :${ETCD_PORT}"
|
||
else
|
||
info "创建并启动 Etcd 容器..."
|
||
# bitnamilegacy/etcd 容器内以 uid=1001 运行,宿主目录需提前授权
|
||
mkdir -p "${DATA_DIR}/etcd"
|
||
chown -R 1001:1001 "${DATA_DIR}/etcd" 2>/dev/null || \
|
||
chmod -R 777 "${DATA_DIR}/etcd"
|
||
|
||
docker run -d \
|
||
--name dev-etcd \
|
||
--restart unless-stopped \
|
||
-p "${ETCD_PORT}:2379" \
|
||
-v "${DATA_DIR}/etcd:/etcd-data" \
|
||
-e ALLOW_NONE_AUTHENTICATION=yes \
|
||
-e ETCD_DATA_DIR=/etcd-data \
|
||
"${LOG_OPTS[@]}" \
|
||
bitnamilegacy/etcd:3.5.13 \
|
||
> /dev/null
|
||
sleep 2
|
||
success "Etcd 容器已创建并启动 :${ETCD_PORT}"
|
||
fi
|
||
|
||
if docker exec dev-etcd etcdctl endpoint health 2>/dev/null | grep -q 'is healthy'; then
|
||
success "Etcd 连通性: healthy ✓"
|
||
else
|
||
warn "Etcd 未返回健康状态,可能仍在启动中"
|
||
fi
|
||
start_docker_logger "dev-etcd"
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# MinIO(S3 兼容对象存储,映射到宿主机供本机与外网访问)
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
step "MinIO"
|
||
|
||
MINIO_API_PORT="${MINIO_API_PORT:-9000}"
|
||
MINIO_CONSOLE_PORT="${MINIO_CONSOLE_PORT:-9001}"
|
||
MINIO_ROOT_USER="${MINIO_ROOT_USER:-minioadmin}"
|
||
MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD:-openIM123minio}"
|
||
MINIO_BUCKET="${MINIO_BUCKET:-openim}"
|
||
MINIO_EXTERNAL_ADDRESS="${MINIO_EXTERNAL_ADDRESS:-http://${DEPLOY_TEST_IP}:${MINIO_API_PORT}}"
|
||
|
||
if docker ps --format '{{.Names}}' | grep -q '^dev-minio$'; then
|
||
success "MinIO 已在运行 (container=dev-minio) API :${MINIO_API_PORT} Console :${MINIO_CONSOLE_PORT}"
|
||
elif docker ps -a --format '{{.Names}}' | grep -q '^dev-minio$'; then
|
||
info "重新启动已有容器 dev-minio..."
|
||
docker start dev-minio > /dev/null
|
||
success "MinIO 已启动 API :${MINIO_API_PORT}"
|
||
else
|
||
info "创建并启动 MinIO 容器..."
|
||
mkdir -p "${DATA_DIR}/minio"
|
||
docker run -d \
|
||
--name dev-minio \
|
||
--restart unless-stopped \
|
||
-p "${MINIO_API_PORT}:9000" \
|
||
-p "${MINIO_CONSOLE_PORT}:9001" \
|
||
-e MINIO_ROOT_USER="${MINIO_ROOT_USER}" \
|
||
-e MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD}" \
|
||
-e MINIO_SERVER_URL="${MINIO_EXTERNAL_ADDRESS}" \
|
||
-v "${DATA_DIR}/minio:/data" \
|
||
"${LOG_OPTS[@]}" \
|
||
minio/minio:latest \
|
||
server /data --console-address ":9001" \
|
||
> /dev/null
|
||
success "MinIO 容器已创建 API :${MINIO_API_PORT} Console :${MINIO_CONSOLE_PORT}"
|
||
fi
|
||
|
||
sleep 2
|
||
if docker run --rm --network container:dev-minio minio/mc:latest \
|
||
sh -c "mc alias set local http://127.0.0.1:9000 '${MINIO_ROOT_USER}' '${MINIO_ROOT_PASSWORD}' && mc mb local/${MINIO_BUCKET} --ignore-existing" \
|
||
&>/dev/null; then
|
||
success "MinIO bucket「${MINIO_BUCKET}」已就绪"
|
||
else
|
||
warn "MinIO bucket 初始化未确认(可稍后手动: mc mb)"
|
||
fi
|
||
info " 本机 API: 127.0.0.1:${MINIO_API_PORT} 外网: ${MINIO_EXTERNAL_ADDRESS}(须放行 TCP ${MINIO_API_PORT})"
|
||
start_docker_logger "dev-minio"
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# LiveKit(本地 Docker 容器,复用 dev-redis)
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
step "LiveKit"
|
||
|
||
LK_CONF="$ROOT_DIR/livekit/livekit.yaml"
|
||
|
||
if [[ ! -f "$LK_CONF" ]]; then
|
||
warn "livekit/livekit.yaml 不存在,请先执行: ./deploy-test/02-patch-config.sh"
|
||
else
|
||
if docker ps --format '{{.Names}}' | grep -q '^dev-livekit$'; then
|
||
success "LiveKit 已在运行 (container=dev-livekit) :7880"
|
||
elif docker ps -a --format '{{.Names}}' | grep -q '^dev-livekit$'; then
|
||
info "重新启动已有容器 dev-livekit..."
|
||
docker start dev-livekit > /dev/null
|
||
sleep 2
|
||
success "LiveKit 已启动 :7880"
|
||
else
|
||
info "创建并启动 LiveKit 容器(首次拉取镜像需要一点时间)..."
|
||
|
||
EXTRA_HOSTS=""
|
||
[[ "$(uname -s)" == "Linux" ]] && EXTRA_HOSTS="--add-host host.docker.internal:host-gateway"
|
||
|
||
# shellcheck disable=SC2086
|
||
docker run -d \
|
||
--name dev-livekit \
|
||
--restart unless-stopped \
|
||
-p 7880:7880 \
|
||
-p 7882:7882/tcp \
|
||
-p 7882:7882/udp \
|
||
-p 50000-51000:50000-51000/udp \
|
||
-v "${LK_CONF}:/etc/livekit.yaml:ro" \
|
||
"${LOG_OPTS[@]}" \
|
||
$EXTRA_HOSTS \
|
||
livekit/livekit-server:latest \
|
||
--config /etc/livekit.yaml \
|
||
> /dev/null
|
||
|
||
sleep 3
|
||
if docker ps --format '{{.Names}}' | grep -q '^dev-livekit$'; then
|
||
success "LiveKit 容器已创建并启动 :7880"
|
||
info " 公网 IP: ${LIVEKIT_NODE_IP} (WebRTC 媒体流直连)"
|
||
info " API Key: ${LIVEKIT_API_KEY}"
|
||
else
|
||
error "LiveKit 启动失败,查看日志: ./deploy-test/logs.sh livekit"
|
||
fi
|
||
fi
|
||
start_docker_logger "dev-livekit"
|
||
fi
|
||
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
# 汇总
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
echo ""
|
||
echo -e "${BOLD}基础设施状态:${NC}"
|
||
print_container_status "Redis" "dev-redis" "${REDIS_PORT}"
|
||
print_container_status "Kafka" "dev-kafka" "${KAFKA_PORT}"
|
||
print_container_status "Etcd" "dev-etcd" "${ETCD_PORT}"
|
||
print_container_status "MinIO" "dev-minio" "${MINIO_API_PORT:-9000}"
|
||
print_container_status "LiveKit" "dev-livekit" "7880"
|
||
echo ""
|
||
echo -e "${BOLD}日志目录:${NC}"
|
||
echo " Docker 容器日志: $DOCKER_LOG_DIR/"
|
||
echo " 本脚本执行日志: $_CURRENT_SCRIPT_LOG"
|
||
echo ""
|
||
echo -e " LiveKit 公网: ${LIVEKIT_NODE_IP}:50000-51000/udp (WebRTC 媒体流)"
|
||
echo -e " Kafka 外网 bootstrap(若已映射): ${DEPLOY_TEST_IP}:${KAFKA_EXTERNAL_PORT:-9094} TCP PLAINTEXT"
|
||
echo ""
|
||
success "Docker 基础设施已就绪!"
|
||
echo ""
|
||
echo -e "${BOLD}下一步:${NC}"
|
||
echo -e " 编译所有后端 Go 服务:"
|
||
echo -e " ${CYAN}./deploy-test/04-build.sh${NC}"
|