一.前言
以下是一个基于 Docker + Shell脚本 的半自动化部署方案,包含镜像构建、容器管理、网络配置和日志监控等核心功能,适用于大多数Web应用或微服务项目。
二.目录结构
三.脚本代码实现
1.Shell
脚本实现 (deploy.sh
)
#!/bin/bash
# 设置颜色代码
E="\033[0m"
R="\033[1;31m"
G="\033[1;32m"
Y="\033[1;33m"
C="\033[1;36m"
BASE_DIR=$(pwd)
BASE_NAME=$(basename "${BASE_DIR}")
DOCKER_COMPOSE_BIN=${DOCKER_COMPOSE_BIN:-docker-compose}
# 检查 docker-compose 是否已安装
if ! command -v $DOCKER_COMPOSE_BIN &> /dev/null; then
echo "docker-compose could not be found, please install it first."
exit 1
fi
# 显示帮助信息
show_help() {
echo -e "${Y}---------------------------------------------------------------${E}"
echo -e "${Y}|--------------------------- HELP ---------------------------|${E}"
echo -e "${Y}---------------------------------------------------------------${G}"
echo -e "${C}【启动服务】./deploy.sh start [all|服务名称]"
echo -e "${G} 示例:./deploy.sh start lm-llmtest-dev\n"
echo -e "${C}【停止服务】./deploy.sh stop [all|服务名称]"
echo -e "${G} 示例:./deploy.sh stop lm-llmtest-dev\n"
echo -e "${C}【重启服务】./deploy.sh restart [all|服务名称]"
echo -e "${G} 示例:./deploy.sh restart lm-llmtest-dev\n"
echo -e "${C}【显示所有服务容器名称】./deploy.sh services"
echo -e "${G} 示例:./deploy.sh services\n"
echo -e "${C}【加载当前服务群组的所有镜像】"
echo -e "${G} 示例:./deploy.sh load \n"
echo -e "######### 指定系统部署初始化【全新部署之前须先执行!!】##########"
echo -e "${C}【初始化服务数据挂载目录】./deploy.sh init datadir [数据目录,/var/llmtest_dev]"
echo -e "${G} 示例:./deploy.sh init datadir /var/llmtest_dev \n"
echo -e "${C}【初始化数据库配置信息】./deploy.sh init database [数据目录] [数据库启动模式(host/docker)] [dockers模式时容器名称]"
echo -e "${G} 示例:./deploy.sh init database /var/llmtest_dev host"
echo -e "${G} 示例:./deploy.sh init database /var/llmtest_dev docker lm-mariadb-dev"
echo -e "${Y}---------------------------------------------------------------${E}"
}
# 加载镜像
load_images() {
echo -e "${G}Loading local images! Please wait...${E}"
docker_images=('nginx-1.25.1.tar.gz' 'mariadb-10.6.tar.gz' 'nginx-1.26.tar.gz' 'python-3.11.13.tar.gz' 'redis-7.2.4.tar.gz' 'etcd-v3.5.15.tar.gz' 'elasticsearch-8.12.2.tar.gz' 'langchain-v4.tar.gz')
for image in "${docker_images[@]}"; do
image_path="${BASE_DIR}/../images/${image}"
if [ ! -f "${image_path}" ]; then
echo -e "${R}Error: File ${image} not found!${E}"
continue
fi
if docker load < "${image_path}" &> /dev/null; then
echo -e "${Y}Loading ${image}\t\t\t ${G}Success${E}"
else
echo -e "${Y}Loading ${image}\t\t\t ${R}Failure${E}"
fi
done
}
# 初始化函数
initialize() {
if [ "$1" = "datadir" ]; then
# 初始化服务数据挂载目录
if mkdir -p "$2"/{llmtest,useradmin,redis,mariadb} && mkdir -p "$2/mariadb/datasource" && mkdir -p "$2/logs/"{useradmin,llmtest}; then
echo -e "${G}Initialization Success.${E}"
echo -e "${G}Created directories:${E}"
# 打印创建的目录列表
for dir in "$2"/llmtest "$2"/useradmin "$2"/redis "$2"/mariadb "$2"/mariadb/datasource "$2/logs/useradmin" "$2/logs/llmtest"; do
if [ -d "$dir" ]; then
echo -e "${G}- $dir${E}"
else
echo -e "${R}- Failed to create $dir${E}"
fi
done
else
echo -e "${R}Initialization Failed.${E}"
exit 1
fi
elif [ "$1" = "database" ]; then
# 初始化数据库
mkdir -p "$2"/{logs,mariadb} && mkdir -p "$2/mariadb/datasource" && cp -a datasource/* "$2/mariadb/datasource" &&
if [ "$3" = "docker" ]; then
local container_name=$4
container_id=$(docker ps -qf "name=$container_name")
echo -e "container_name -> $container_name,container_id -> $container_id"
if [ -z "$container_id" ]; then
echo -e "${R}Docker container "name=$container_name" not found.${E}"
exit 1
fi
docker exec $container_id bash "/var/llmtest_dev/mariadb/datasource/init_database.sh" "$2" "$3" && echo -e "${G}Database initialization completed.${E}"
elif [ "$3" = "host" ]; then
bash "$2/mariadb/datasource/init_database.sh" "$2" "$3" && echo -e "${G}Database initialization completed.${E}"
else
echo -e "${R}Unknown mode: $3${E}"
exit 1
fi || { echo -e "${R}Database initialization failed.${E}"; exit 1; }
else
echo -e "${R}Error: Missing required parameters or invalid initialization option.${E}"
exit 1
fi
}
# 开启服务
start_service() {
if [ "$1" = "all" ]; then
if ${DOCKER_COMPOSE_BIN} up -d --build && ${DOCKER_COMPOSE_BIN} ps -a; then
echo -e "${G}All services started successfully.${E}"
${DOCKER_COMPOSE_BIN} logs -f
else
echo -e "${R}Failed to start all services.${E}"
exit 1
fi
else
if [ -z "$1" ]; then
echo -e "${R}Error: Service name is required to start a service.${E}"
exit 1
fi
if ${DOCKER_COMPOSE_BIN} up -d --build "$1" && ${DOCKER_COMPOSE_BIN} ps -a; then
echo -e "${G}Service [$1] started successfully.${E}"
${DOCKER_COMPOSE_BIN} logs -f $1
else
echo -e "${R}Failed to start the service [$1].${E}"
exit 1
fi
fi
}
# 停止并移除服务及其容器和相关镜像
stop_and_remove_service() {
local service_name=$1
if [ -z "$service_name" ]; then
echo -e "${R}Error: Service name is required to stop and remove a service.${E}"
exit 1
fi
# 使用 docker-compose down 来停止并移除服务及其容器
if ! ${DOCKER_COMPOSE_BIN} down -v "$service_name"; then
echo -e "${R}Failed to stop and remove the service [$service_name] and its containers.${E}"
fi
# 获取服务使用的镜像名称
image_names=($(${DOCKER_COMPOSE_BIN} images | grep "$service_name" | awk '{print $2}' | sort -u))
echo -e "image_names -> $image_names"
if [ ${#image_names[@]} -eq 0 ]; then
for image_name in "${image_names[@]}"; do
if [ -n "$image_name" ]; then
# 删除服务镜像
if docker rmi "$image_name"; then
echo -e "${G}Successfully removed image [$image_name] associated with service [$service_name].${E}"
else
echo -e "${R}Failed removed image [$image_name] associated with service [$service_name].${E}"
fi
fi
done
else
# 停止并移除容器
if docker stop "$service_name" && docker rm "$service_name"; then
echo -e "${G}Successfully stopped and removed container [$service_name].${E}"
else
echo -e "${R}Failed to stop and remove container [$service_name].${E}"
fi
fi
}
# 定义不应被删除的基础镜像列表
declare -a base_images=("mariadb" "mysql" "redis" "nginx" "python") # 添加其他你不想删除的基础镜像
# 停止服务并删除容器和相关镜像
stop_service() {
if [ "$1" = "all" ]; then
echo -e "${Y}#警告!!删除项目[${BASE_NAME}]下的所有容器及镜像将不可恢复,不会删除外部挂载数据.${E}"
# 获取与compose相关的所有镜像名称
compose_images=($(${DOCKER_COMPOSE_BIN} images | awk 'NR>1{print $2}' | sort -u))
# 过滤出不是基础镜像的镜像
filtered_images=()
for image_ in "${compose_images[@]}"; do
skip_image=false
for base_image in "${base_images[@]}"; do
if [[ "$image_" == *"$base_image"* ]]; then
skip_image=true
break
fi
done
if ! $skip_image; then
filtered_images+=("$image_")
fi
done
echo -e "The following images will be removed: ${filtered_images[@]}"
# 使用 docker-compose down 来停止所有服务并移除它们的容器
if ! ${DOCKER_COMPOSE_BIN} down -v; then
echo -e "${R}Failed to stop all services and remove containers.${E}"
exit 1
else
echo -e "${G}All services stopped and containers removed successfully.${E}"
fi
# 尝试删除每个找到的非基础镜像
for image_ in "${filtered_images[@]}"; do
if [ -n "$image_" ]; then # 确保镜像名称非空
if docker rmi "$image_" &> /dev/null; then
echo -e "${G}正在移除构建的服务镜像 ${image_} ...... \t\tsuccess${E}"
else
# 如果常规删除失败,则尝试强制删除
if docker rmi -f "$image_" &> /dev/null; then
echo -e "${Y}正在强制移除构建的服务镜像 ${image_} ... \tsuccess (forced)${E}"
else
echo -e "${R}无法移除构建的服务镜像 ${image_}.${E}"
fi
fi
fi
done
# 最后打印剩余镜像列表以供确认
docker images
else
stop_and_remove_service "$1"
fi
}
# 重启服务
restart_service() {
if [ "$1" = "all" ]; then
# 调用 stop_service 函数来停止所有服务
stop_service "all"
# 启动所有服务
if ${DOCKER_COMPOSE_BIN} up -d --build && ${DOCKER_COMPOSE_BIN} ps -a; then
echo -e "${G}All services have been restarted successfully.${E}"
${DOCKER_COMPOSE_BIN} logs -f
else
echo -e "${R}Failed to restart all services.${E}"
exit 1
fi
else
if [ -z "$1" ]; then
echo -e "${R}Error: Service name is required to restart a service.${E}"
exit 1
fi
# 对于单个服务,调用 stop_service 来停止服务,并重新构建和启动服务
local service_name="$1"
stop_service "$service_name"
# 重新构建并启动指定的服务
start_service "$service_name"
fi
}
deploy_service() {
echo -e "${DOCKER_COMPOSE_BIN} config --services"
services=$(${DOCKER_COMPOSE_BIN} config --services)
# 遍历每个服务并查找其容器名称和ID
echo -e "${G}>>>>>> 容器名称和ID <<<<<<\n----------------------${E}"
for service in $services; do
# 查找与服务名匹配的容器信息
containers=$(docker ps --filter "name=$service" --format "{{.Names}}({{.ID}})")
if [ -z "$containers" ]; then
echo -e "${Y}Service: $service -> No containers found.${E}"
else
container_info=""
# 拼接所有容器的名称和ID
for container in $containers; do
container_info="${container_info}${container}"
done
# 输出服务及其对应的容器信息
echo -e "${G}Service: $service ${GREEN}-> Container:${container_info}${E}"
fi
done
echo -e "----------------------${E}"
}
case "$1" in
help)
show_help
;;
load)
load_images
;;
init)
shift
initialize "$@"
;;
start)
shift
start_service "$@"
;;
stop)
shift
stop_service "$@"
;;
restart)
shift
restart_service "$@"
;;
services)
shift
deploy_service "$@"
;;
*)
echo -e "${R}Usage: $0 {help|load|init|start|stop|restart|services} [args]${E}"
exit 1
;;
esac
2.docker-compose.yml
文件脚本实现
services:
# 管理前端
lm-backend-dev:
container_name: lm-backend-dev
image: lm-backend-dev
build:
context: ./
dockerfile: dockerfiles/dockerfile-backendcli
ports:
- "8001:80"
privileged: true
volumes:
- "/etc/localtime:/etc/localtime"
- "/var/llmtest_dev/html:/usr/share/nginx/html"
- "/var/llmtest_dev/logs/cli:/var/log/nginx"
# - "/data/mulsen/llmtest_dev/cli/nginx/etc:/etc/nginx"
# docker run --privileged -itd --name mariadb-10.11 -v /var/data/data:/home/data -v /var/data/etc:/opt/maria/etc mariadb:10.11 /sbin/init&&/bin/bash
# 数据库服务配置
lm-mariadb-dev:
container_name: lm-mariadb-dev
image: mariadb:latest
ports:
- "3318:3306"
privileged: true
environment:
- TZ=Asia/Shanghai
- MYSQL_ROOT_PASSWORD=nwbot#f76m+*
- MYSQL_ROOT_HOST:'%'
# - MYSQL_DATABASES:nwfaq
# - MYSQL_USER:nwbot
# - MYSQL_PASSWORD:nwbot#f76m+*
volumes:
- "/etc/localtime:/etc/localtime"
- "/var/llmtest_dev/mariadb/data:/var/lib/mysql"
- "/var/llmtest_dev/mariadb/etc:/etc/mysql/conf.d"
- "/var/llmtest_dev/mariadb/datasource:/var/llmtest_dev/mariadb/datasource"
# - "/var/llmtest_dev/mariadb/datasource:/docker-entrypoint-initdb.d" # 挂载包含初始化脚本和SQL文件的目录
# 配置文件目录
# redis服务配置
lm-redis-dev:
image: redis:7
container_name: lm-redis-dev
ports:
- "6889:6379"
privileged: true
environment:
- REDIS_PASSWORD=fF76M+@963
volumes:
- "/etc/localtime:/etc/localtime"
- "/var/llmtest_dev/redis/data:/data"
# 用户管理服务
lm-useradmin-dev:
container_name: lm-useradmin-dev
image: lm-useradmin-dev
build:
context: ./
dockerfile: dockerfiles/dockerfile-useradmin
ports:
- "8002:5000"
privileged: true
environment:
- SERVER_IP=127.0.0.1
- SERVER_PORT=57680
volumes:
- "/etc/localtime:/etc/localtime"
- "/var/llmtest_dev/logs/useradmin:/var/app/logs"
# environment:
depends_on:
- lm-redis-dev
- lm-mariadb-dev
# 评测基础数据服务
lm-test-dev:
container_name: lm-test-dev
image: lm-test-dev
build:
context: ./
dockerfile: dockerfiles/dockerfile-test
ports:
- "8003:5000"
privileged: true
environment:
- SERVER_IP=127.0.0.1
- SERVER_PORT=8003
volumes:
- "/etc/localtime:/etc/localtime"
- "/var/llmtest_dev/logs/test:/var/app/logs"
- "/var/llmtest_dev/test/datasets:/var/app/datasets"
depends_on:
- lm-redis-dev
- lm-mariadb-dev
# 评测结果分析服务
lm-evaluation-dev:
container_name: lm-evaluation-dev
image: lm-evaluation-dev
build:
context: ./
dockerfile: dockerfiles/dockerfile-evaluation
ports:
- "8004:5000"
privileged: true
environment:
- SERVER_IP=127.0.0.1
- SERVER_PORT=8004
volumes:
- "/etc/localtime:/etc/localtime"
- "/var/llmtest_dev/logs/evaluation:/var/app/logs"
- "/var/llmtest_dev/test/datasets:/var/app/datasets"
depends_on:
- lm-redis-dev
- lm-mariadb-dev
##############################################################################
3.数据库初始化脚本init_database.sh
#!/bin/bash
# 设置颜色代码
E="\033[0m"
R="\033[1;31m"
G="\033[1;32m"
Y="\033[1;33m"
C="\033[1;36m"
# 获取传入的路径参数和运行模式
BASE_DIR=$1
MODE=$2
echo -e "${Y}BASE_DIR -> $BASE_DIR \033[0m"
echo -e "${Y}MODE -> $MODE ${G}"
# 数据库连接信息
if [ "$MODE" = "docker" ]; then
DB_HOST="localhost" # 在容器内部使用localhost
DB_PORT="3306"
DB_PASSWORD="nwbot#f76m+*"
else
DB_HOST="192.168.211.75" # 根据实际情况设置主机IP
DB_PORT="3306"
DB_PASSWORD="maria"
fi
DB_USER="root"
DB_NAME="test"
SQL_FILE="${BASE_DIR}/mariadb/datasource/test.sql" # 根据传入路径设置SQL文件路径
echo -e "${Y}SQL_FILE -> $SQL_FILE ${G}"
# 创建数据库
mysql --protocol=TCP -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`$DB_NAME\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
# 检查创建数据库是否成功
if [ $? -ne 0 ]; then
echo -e "\033[0;31m创建数据库失败\033[0m"
exit 1
fi
# 导入数据
mysql --protocol=TCP -h$DB_HOST -P$DB_PORT -u$DB_USER -p$DB_PASSWORD $DB_NAME < $SQL_FILE
# 检查导入数据是否成功
if [ $? -eq 0 ]; then
echo -e "${G}【数据库和数据导入】初始化完成\033[0m"
else
echo -e "${G}【导入数据】失败\033[0m"
exit 1
fi
三.总结
通过此方案,可实现从开发到生产的平滑迁移,显著提升部署效率和一致性。
希望对你有所帮助!