OpenIM + LiveKit 内网测试环境一键部署包

- docker-compose: openim-server v3.8.3-patch.12 + mongo/redis/etcd/kafka/minio + livekit v1.13.5
- 按约束移除 openim-chat(GPLv3) 及依赖它的 web/admin 前端; 监控组件收进 profile m
- scripts/deploy.sh 一键部署, scripts/selftest.sh 自测(注册/登录/文字/语音/文件/一对一通话), scripts/restart.sh 重启
- README: 部署记录(服务器要求/端口表/运维/接入参数)
This commit is contained in:
KIMI
2026-08-08 22:07:55 +08:00
commit 33fa2339e2
12 changed files with 1048 additions and 0 deletions
+387
View File
@@ -0,0 +1,387 @@
# 内部通讯 App —— OpenIM + LiveKit 内网测试环境
# 基于官方 openim-docker v3.8 裁剪:
# - 移除 openim-chatGPLv3,本项目的注册登录后续自研)
# - 移除 openim-web-front / openim-admin-front(均依赖 chat 服务)
# - 新增 livekit 服务(语音通话 SFU
# 监控组件(prometheus/alertmanager/grafana/node-exporter)保留在 profile "m" 下,
# 默认不启动;需要时执行 docker compose --profile m up -d。
networks:
openim:
driver: bridge
services:
mongo:
image: "${MONGO_IMAGE}"
container_name: mongo
command: >
bash -c '
docker-entrypoint.sh mongod --wiredTigerCacheSizeGB $$wiredTigerCacheSizeGB --auth &
until mongosh -u $$MONGO_INITDB_ROOT_USERNAME -p $$MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "db.runCommand({ ping: 1 })" &>/dev/null; do
echo "Waiting for MongoDB to start..."
sleep 1
done &&
mongosh -u $$MONGO_INITDB_ROOT_USERNAME -p $$MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "
db = db.getSiblingDB(\"$$MONGO_INITDB_DATABASE\");
if (!db.getUser(\"$$MONGO_OPENIM_USERNAME\")) {
db.createUser({
user: \"$$MONGO_OPENIM_USERNAME\",
pwd: \"$$MONGO_OPENIM_PASSWORD\",
roles: [{role: \"readWrite\", db: \"$$MONGO_INITDB_DATABASE\"}]
});
print(\"User created successfully: \");
print(\"Username: $$MONGO_OPENIM_USERNAME\");
print(\"Password: $$MONGO_OPENIM_PASSWORD\");
print(\"Database: $$MONGO_INITDB_DATABASE\");
} else {
print(\"User already exists in database: $$MONGO_INITDB_DATABASE, Username: $$MONGO_OPENIM_USERNAME\");
}
" &&
tail -f /dev/null
'
volumes:
- "${DATA_DIR}/components/mongodb/data/db:/data/db"
- "${DATA_DIR}/components/mongodb/data/logs:/data/logs"
- "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo"
environment:
- TZ=Asia/Shanghai
- wiredTigerCacheSizeGB=1
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=openIM123
- MONGO_INITDB_DATABASE=openim_v3
- MONGO_OPENIM_USERNAME=${MONGO_USERNAME}
- MONGO_OPENIM_PASSWORD=${MONGO_PASSWORD}
restart: always
networks:
- openim
redis:
image: "${REDIS_IMAGE}"
container_name: redis
volumes:
- "${DATA_DIR}/components/redis/data:/data"
- "${DATA_DIR}/components/redis/config/redis.conf:/usr/local/redis/config/redis.conf"
environment:
TZ: Asia/Shanghai
restart: always
sysctls:
net.core.somaxconn: 1024
command:
[
"redis-server",
"/usr/local/redis/config/redis.conf",
"--requirepass",
"${REDIS_PASSWORD}",
"--appendonly",
"yes",
]
networks:
- openim
etcd:
image: "${ETCD_IMAGE}"
container_name: etcd
ports:
- "12379:2379"
- "12380:2380"
environment:
- ETCD_NAME=s1
- ETCD_DATA_DIR=/etcd-data
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_ADVERTISE_CLIENT_URLS=http://0.0.0.0:2379
- ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380
- ETCD_INITIAL_ADVERTISE_PEER_URLS=http://0.0.0.0:2380
- ETCD_INITIAL_CLUSTER=s1=http://0.0.0.0:2380
- ETCD_INITIAL_CLUSTER_TOKEN=tkn
- ETCD_INITIAL_CLUSTER_STATE=new
- ALLOW_NONE_AUTHENTICATION=no
## Optional: Enable etcd authentication by setting the following credentials
# - ETCD_ROOT_USER=root
# - ETCD_ROOT_PASSWORD=openIM123
# - ETCD_USERNAME=openIM
# - ETCD_PASSWORD=openIM123
volumes:
- "${DATA_DIR}/components/etcd:/etcd-data"
command: >
/bin/sh -c '
etcd &
export ETCDCTL_API=3
echo "Waiting for etcd to become healthy..."
until etcdctl --endpoints=http://127.0.0.1:2379 endpoint health &>/dev/null; do
echo "Waiting for ETCD to start..."
sleep 1
done
echo "etcd is healthy."
if [ -n "$${ETCD_ROOT_USER}" ] && [ -n "$${ETCD_ROOT_PASSWORD}" ] && [ -n "$${ETCD_USERNAME}" ] && [ -n "$${ETCD_PASSWORD}" ]; then
echo "Authentication credentials provided. Setting up authentication..."
echo "Checking authentication status..."
if ! etcdctl --endpoints=http://127.0.0.1:2379 auth status | grep -q "Authentication Status: true"; then
echo "Authentication is disabled. Creating users and enabling..."
# Create users and setup permissions
etcdctl --endpoints=http://127.0.0.1:2379 user add $${ETCD_ROOT_USER} --new-user-password=$${ETCD_ROOT_PASSWORD} || true
etcdctl --endpoints=http://127.0.0.1:2379 user add $${ETCD_USERNAME} --new-user-password=$${ETCD_PASSWORD} || true
etcdctl --endpoints=http://127.0.0.1:2379 role add openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 role grant-permission openim-role --prefix=true readwrite / || true
etcdctl --endpoints=http://127.0.0.1:2379 role grant-permission openim-role --prefix=true readwrite "" || true
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_USERNAME} openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_ROOT_USER} $${ETCD_USERNAME} root || true
echo "Enabling authentication..."
etcdctl --endpoints=http://127.0.0.1:2379 auth enable
echo "Authentication enabled successfully"
else
echo "Authentication is already enabled. Checking OpenIM user..."
# Check if openIM user exists and can perform operations
if ! etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} put /test/auth "auth-check" &>/dev/null; then
echo "OpenIM user test failed. Recreating user with root credentials..."
# Try to create/update the openIM user using root credentials
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} user add $${ETCD_USERNAME} --new-user-password=$${ETCD_PASSWORD} --no-password-file || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role add openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role grant-permission openim-role --prefix=true readwrite / || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role grant-permission openim-role --prefix=true readwrite "" || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} user grant-role $${ETCD_USERNAME} openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_ROOT_USER} $${ETCD_USERNAME} root || true
echo "OpenIM user recreated with required permissions"
else
echo "OpenIM user exists and has correct permissions"
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} del /test/auth &>/dev/null
fi
fi
echo "Testing authentication with OpenIM user..."
if etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} put /test/auth "auth-works"; then
echo "Authentication working properly"
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} del /test/auth
else
echo "WARNING: Authentication test failed"
fi
else
echo "No authentication credentials provided. Running in no-auth mode."
echo "To enable authentication, set ETCD_ROOT_USER, ETCD_ROOT_PASSWORD, ETCD_USERNAME, and ETCD_PASSWORD environment variables."
fi
tail -f /dev/null
'
restart: always
networks:
- openim
kafka:
image: "${KAFKA_IMAGE}"
container_name: kafka
user: root
restart: always
volumes:
- "${DATA_DIR}/components/kafka:/bitnami/kafka"
environment:
#KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m"
TZ: Asia/Shanghai
# Unique identifier for the Kafka node (required in controller mode)
KAFKA_CFG_NODE_ID: 0
# Defines the roles this Kafka node plays: broker, controller, or both
KAFKA_CFG_PROCESS_ROLES: controller,broker
# Specifies which nodes are controller nodes for quorum voting.
# The syntax follows the KRaft mode (no ZooKeeper): node.id@host:port
# The controller listener endpoint here is kafka:9093
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093
# Specifies which listener is used for controller-to-controller communication
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
# Default number of partitions for new topics
KAFKA_NUM_PARTITIONS: 8
# Whether to enable automatic topic creation
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
# Kafka internal listeners; Kafka supports multiple ports with different protocols
# Each port is used for a specific purpose: INTERNAL for internal broker communication,
# CONTROLLER for controller communication, EXTERNAL for external client connections.
# These logical listener names are mapped to actual protocols via KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP
# In short, Kafka is listening on three logical ports: 9092 for internal communication,
# 9093 for controller traffic, and 9094 for external access.
KAFKA_CFG_LISTENERS: "INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:9094"
# Addresses advertised to clients. INTERNAL://kafka:9092 uses the internal Docker service name 'kafka',
# so other containers can access Kafka via kafka:9092.
# EXTERNAL://localhost:19094 is the address external clients (e.g., in the LAN) should use to connect.
# If Kafka is deployed on a different machine than IM, 'localhost'(or 'kafka') should be replaced with the LAN IP.
KAFKA_CFG_ADVERTISED_LISTENERS: "INTERNAL://kafka:9092,EXTERNAL://kafka:9094"
# Maps logical listener names to actual protocols.
# Supported protocols include: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT"
# Defines which listener is used for inter-broker communication within the Kafka cluster
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: "INTERNAL"
# Authentication configuration variables - comment out to disable auth
# KAFKA_USERNAME: "openIM"
# KAFKA_PASSWORD: "openIM123"
command: >
/bin/sh -c '
if [ -n "$${KAFKA_USERNAME}" ] && [ -n "$${KAFKA_PASSWORD}" ]; then
echo "=== Kafka SASL Authentication ENABLED ==="
echo "Username: $${KAFKA_USERNAME}"
# Set environment variables for SASL authentication
export KAFKA_CFG_LISTENERS="SASL_PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094"
export KAFKA_CFG_ADVERTISED_LISTENERS="SASL_PLAINTEXT://kafka:9092,EXTERNAL://kafka:9094"
export KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP="CONTROLLER:PLAINTEXT,EXTERNAL:SASL_PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT"
export KAFKA_CFG_SASL_ENABLED_MECHANISMS="PLAIN"
export KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL="PLAIN"
export KAFKA_CFG_INTER_BROKER_LISTENER_NAME="SASL_PLAINTEXT"
export KAFKA_CLIENT_USERS="$${KAFKA_USERNAME}"
export KAFKA_CLIENT_PASSWORDS="$${KAFKA_PASSWORD}"
fi
# Start Kafka with the configured environment
exec /opt/bitnami/scripts/kafka/entrypoint.sh /opt/bitnami/scripts/kafka/run.sh
'
networks:
- openim
minio:
image: "${MINIO_IMAGE}"
ports:
- "${MINIO_PORT}:9000"
- "${MINIO_CONSOLE_PORT}:9090"
container_name: minio
volumes:
- "${DATA_DIR}/components/mnt/data:/data"
- "${DATA_DIR}/components/mnt/config:/root/.minio"
environment:
TZ: Asia/Shanghai
MINIO_ROOT_USER: "${MINIO_ACCESS_KEY_ID}"
MINIO_ROOT_PASSWORD: "${MINIO_SECRET_ACCESS_KEY}"
restart: always
command: minio server /data --console-address ':9090'
networks:
- openim
prometheus:
image: ${PROMETHEUS_IMAGE}
container_name: prometheus
restart: always
user: root
profiles:
- m
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml
- ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
- ${DATA_DIR}/components/prometheus/data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.listen-address=:${PROMETHEUS_PORT}"
network_mode: host
alertmanager:
image: ${ALERTMANAGER_IMAGE}
container_name: alertmanager
restart: always
profiles:
- m
volumes:
- ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
- ./config/email.tmpl:/etc/alertmanager/email.tmpl
command:
- "--config.file=/etc/alertmanager/alertmanager.yml"
- "--web.listen-address=:${ALERTMANAGER_PORT}"
network_mode: host
grafana:
image: ${GRAFANA_IMAGE}
container_name: grafana
user: root
restart: always
profiles:
- m
environment:
- GF_SECURITY_ALLOW_EMBEDDING=true
- GF_SESSION_COOKIE_SAMESITE=none
- GF_SESSION_COOKIE_SECURE=true
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
- GF_SERVER_HTTP_PORT=${GRAFANA_PORT}
volumes:
- ${DATA_DIR:-./}/components/grafana:/var/lib/grafana
network_mode: host
node-exporter:
image: ${NODE_EXPORTER_IMAGE}
container_name: node-exporter
restart: always
profiles:
- m
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- "--path.procfs=/host/proc"
- "--path.sysfs=/host/sys"
- "--path.rootfs=/rootfs"
- "--web.listen-address=:${NODE_EXPORTER_PORT}"
network_mode: host
openim-server:
image: ${OPENIM_SERVER_IMAGE}
container_name: openim-server
init: true
ports:
- "${OPENIM_MSG_GATEWAY_PORT}:10001"
- "${OPENIM_API_PORT}:10002"
healthcheck:
test: ["CMD", "sh", "-c", "mage check"]
interval: 5s
timeout: 60s
retries: 10
environment:
- IMENV_MONGODB_ADDRESS=${MONGO_ADDRESS}
- IMENV_MONGODB_USERNAME=${MONGO_USERNAME}
- IMENV_MONGODB_PASSWORD=${MONGO_PASSWORD}
- IMENV_KAFKA_ADDRESS=${KAFKA_ADDRESS}
- IMENV_KAFKA_USERNAME=${KAFKA_USERNAME}
- IMENV_KAFKA_PASSWORD=${KAFKA_PASSWORD}
- IMENV_DISCOVERY_ETCD_ADDRESS=${ETCD_ADDRESS}
- IMENV_REDIS_ADDRESS=${REDIS_ADDRESS}
- IMENV_REDIS_PASSWORD=${REDIS_PASSWORD}
- IMENV_DISCOVERY_ETCD_USERNAME=${ETCD_USERNAME}
- IMENV_DISCOVERY_ETCD_PASSWORD=${ETCD_PASSWORD}
- IMENV_MINIO_INTERNALADDRESS=${MINIO_INTERNAL_ADDRESS}
- IMENV_MINIO_EXTERNALADDRESS=${MINIO_EXTERNAL_ADDRESS}
- IMENV_MINIO_ACCESSKEYID=${MINIO_ACCESS_KEY_ID}
- IMENV_MINIO_SECRETACCESSKEY=${MINIO_SECRET_ACCESS_KEY}
- IMENV_SHARE_SECRET=${OPENIM_SECRET}
- IMENV_LOG_ISSTDOUT=${LOG_IS_STDOUT}
- IMENV_LOG_REMAINLOGLEVEL=${LOG_LEVEL}
- IMENV_OPENIM_API_PROMETHEUS_GRAFANAURL=${GRAFANA_URL}
restart: always
depends_on:
- mongo
- redis
- etcd
- kafka
- minio
networks:
- openim
# LiveKit:语音/视频通话的 SFUApache 2.0,自部署。
# 客户端通过 ws://<SERVER_IP>:7880 连接,凭 LIVEKIT_API_KEY/SECRET 签发的 token 进房。
livekit:
image: ${LIVEKIT_IMAGE}
container_name: livekit
restart: always
command: --config /etc/livekit.yaml --node-ip ${SERVER_IP}
environment:
LIVEKIT_KEYS: "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
ports:
- "${LIVEKIT_PORT}:7880" # HTTP / WebSocket 信令
- "7881:7881" # RTC over TCPUDP 不通时的备用通道)
- "7882:7882/udp" # RTC 媒体(UDP 复用端口)
volumes:
- ./config/livekit.yaml:/etc/livekit.yaml
networks:
- openim