Files
deploy-test/03-start-infra.sh
2026-04-13 22:25:56 +07:00

315 lines
14 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
# =============================================================================
# 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"
# ──────────────────────────────────────────────────────────────────────────────
# KafkaKRaft 模式)
# ──────────────────────────────────────────────────────────────────────────────
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
# 双 listenerINTERNAL 供本机服务(与 kafka.yml 127.0.0.1 一致EXTERNAL 供公网 bootstrapadvertise 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}EXTERNALPLAINTEXT请在安全组放行 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"
# ──────────────────────────────────────────────────────────────────────────────
# MinIOS3 兼容对象存储,映射到宿主机供本机与外网访问)
# ──────────────────────────────────────────────────────────────────────────────
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}"