一、当Shell脚本遇上Docker:天生一对的好搭档

想象一下,你每天都要重复执行几十次"docker build"和"docker run"命令,还要手动管理容器网络和存储卷。这种日子是不是让你抓狂?别担心,Shell脚本就是来拯救你的超级英雄!它和Docker的结合,就像咖啡遇上奶泡,能调出效率的美味拿铁。

Shell脚本可以帮我们把繁琐的Docker操作封装成简单的命令。比如下面这个启动MySQL服务的例子:

#!/bin/bash
# 定义变量
CONTAINER_NAME="mysql_dev"
MYSQL_PASSWORD="safe_password123"
DATA_DIR="/opt/mysql_data"

# 检查数据目录是否存在
if [ ! -d "$DATA_DIR" ]; then
    mkdir -p $DATA_DIR
    echo "创建数据目录: $DATA_DIR"
fi

# 运行MySQL容器
docker run -d \
    --name $CONTAINER_NAME \
    -e MYSQL_ROOT_PASSWORD=$MYSQL_PASSWORD \
    -v $DATA_DIR:/var/lib/mysql \
    -p 3306:3306 \
    mysql:8.0 \
    --default-authentication-plugin=mysql_native_password

# 检查容器状态
if [ $(docker inspect -f '{{.State.Running}}' $CONTAINER_NAME) = "true" ]; then
    echo "MySQL容器启动成功!"
else
    echo "警告:MySQL容器启动异常!"
fi

这个脚本做了几件很酷的事:

  1. 自动创建数据存储目录
  2. 用安全的方式设置MySQL密码
  3. 挂载数据卷实现持久化
  4. 自动检查容器是否正常运行

二、进阶玩法:多容器编排与管理

当项目变得复杂时,单个容器往往不够用。我们需要同时管理多个容器,并让它们协同工作。下面这个示例展示了如何用Shell脚本编排一个简单的Web应用栈(Nginx + Node.js + Redis):

#!/bin/bash
# 定义网络
NETWORK_NAME="myapp_network"
docker network create $NETWORK_NAME

# 启动Redis服务
docker run -d \
    --name redis \
    --network $NETWORK_NAME \
    redis:6

# 启动Node.js应用
docker run -d \
    --name node_app \
    --network $NETWORK_NAME \
    -v $(pwd)/app:/usr/src/app \
    -w /usr/src/app \
    node:16 \
    sh -c "npm install && npm start"

# 启动Nginx反向代理
docker run -d \
    --name nginx \
    --network $NETWORK_NAME \
    -p 80:80 \
    -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf \
    nginx:latest

echo "应用栈部署完成!访问 http://localhost"

这个脚本的亮点在于:

  1. 创建了专用Docker网络让容器间可以互相通信
  2. 使用数据卷挂载应用代码和配置
  3. 自动构建Node.js依赖
  4. 通过Nginx暴露服务

三、自动化构建与部署:让CI/CD更简单

在持续集成环境中,我们经常需要自动构建Docker镜像并推送到仓库。下面这个脚本可以集成到你的Jenkins或GitLab CI中:

#!/bin/bash
# 参数检查
if [ $# -lt 2 ]; then
    echo "用法: $0 <项目目录> <镜像标签>"
    exit 1
fi

PROJECT_DIR=$1
IMAGE_TAG=$2
REGISTRY="registry.mycompany.com:5000"

# 进入项目目录
cd $PROJECT_DIR || exit 1

# 构建Docker镜像
echo "开始构建镜像..."
docker build -t $IMAGE_TAG .

# 标记并推送镜像
echo "标记镜像..."
docker tag $IMAGE_TAG $REGISTRY/$IMAGE_TAG

echo "推送镜像到仓库..."
docker push $REGISTRY/$IMAGE_TAG

# 清理本地镜像
echo "清理..."
docker rmi $IMAGE_TAG $REGISTRY/$IMAGE_TAG

echo "构建流程完成!镜像地址: $REGISTRY/$IMAGE_TAG"

这个脚本展示了:

  1. 参数化输入提高复用性
  2. 完整的构建-标记-推送流程
  3. 自动清理避免占用磁盘空间
  4. 详细的执行日志输出

四、实用技巧与避坑指南

在实际使用中,我总结了一些非常有用的技巧:

  1. 容器健康检查:在脚本中添加健康检查逻辑
# 等待容器健康状态
wait_for_healthy() {
    local container=$1
    local timeout=${2:-60}
    
    echo "等待 $container 变为健康状态..."
    for i in $(seq 1 $timeout); do
        status=$(docker inspect --format='{{.State.Health.Status}}' $container)
        if [ "$status" = "healthy" ]; then
            echo "$container 已健康运行!"
            return 0
        fi
        sleep 1
    done
    
    echo "超时:$container 未达到健康状态"
    return 1
}

# 使用示例
wait_for_healthy "mysql_dev" 120
  1. 日志收集:自动收集容器日志到文件
# 收集最近1小时的日志
collect_logs() {
    local container=$1
    local log_file="logs/${container}_$(date +%Y%m%d%H%M).log"
    
    mkdir -p logs
    echo "收集 $container 日志到 $log_file..."
    docker logs --since 1h $container > $log_file 2>&1
}
  1. 安全提示:不要在脚本中硬编码密码!应该使用:
# 从环境变量读取密码
if [ -z "$DB_PASSWORD" ]; then
    echo "错误:DB_PASSWORD环境变量未设置"
    exit 1
fi

docker run -e MYSQL_PASSWORD=$DB_PASSWORD ...

五、应用场景与技术选型

这种技术组合特别适合以下场景:

  • 开发环境快速搭建
  • 自动化测试环境准备
  • 小型项目的轻量级部署
  • 日常运维任务自动化

技术优点

  1. 极低的学习曲线(只要会基础Shell和Docker命令)
  2. 无需额外依赖,所有Linux系统原生支持
  3. 灵活性强,可以处理各种定制化需求
  4. 调试方便,可以逐步执行查看效果

技术局限

  1. 不适合超大规模容器编排(这种情况下应该用Kubernetes)
  2. 脚本可能变得难以维护(需要良好的代码组织)
  3. 缺乏原生的事务支持(需要自己实现回滚逻辑)

注意事项

  1. 始终处理错误情况(使用set -euo pipefail)
  2. 为脚本添加详细的日志输出
  3. 考虑添加参数校验和帮助信息
  4. 重要操作前添加确认提示

六、总结与展望

通过Shell脚本与Docker的集成,我们实现了从单一容器管理到复杂应用栈编排的自动化。这种轻量级方案特别适合中小型项目,能够在保持简单性的同时提供强大的自动化能力。

未来可以进一步考虑:

  1. 与Ansible等配置管理工具集成
  2. 添加更完善的错误恢复机制
  3. 实现蓝绿部署等高级部署策略
  4. 集成监控告警功能

记住,自动化不是为了炫技,而是为了让生活更轻松。从今天开始,把你重复的Docker操作变成脚本吧!