MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Bash中的脚本与Docker容器

2023-10-194.0k 阅读

Bash 脚本基础

Bash(Bourne Again SHell)是一种广泛使用的命令行解释器,在Linux和类Unix系统中占据着核心地位。Bash脚本是由一系列Bash命令组成的文本文件,这些命令按照顺序执行,能够自动化完成各种系统管理和日常任务。

脚本的创建与执行

创建一个Bash脚本非常简单,只需使用文本编辑器(如vi、nano等)创建一个新文件,并在文件开头添加 #!/bin/bash 这一行,这被称为Shebang,它告诉系统使用 /bin/bash 来解释执行该脚本。例如,创建一个名为 hello.sh 的脚本:

#!/bin/bash
echo "Hello, World!"

为了使脚本可执行,需要修改文件的权限,赋予其执行权限:

chmod +x hello.sh

然后可以通过以下方式执行脚本:

./hello.sh

变量

在Bash脚本中,变量用于存储数据。变量的定义很简单,不需要指定数据类型。例如:

name="John"
echo "My name is $name"

这里定义了一个名为 name 的变量,并将其值设置为 "John"。在使用变量时,需要在变量名前加上 $ 符号。

Bash脚本还支持环境变量,这些变量由系统或父进程传递而来。例如,$PATH 环境变量包含了系统搜索可执行文件的路径列表。可以通过 echo $PATH 来查看其值。

条件语句

条件语句允许根据不同的条件执行不同的代码块。Bash中最常用的条件语句是 if - then - else 结构。例如:

num=10
if [ $num -gt 5 ]; then
    echo "The number is greater than 5"
else
    echo "The number is less than or equal to 5"
fi

在上述代码中,使用 [ $num -gt 5 ] 来判断变量 $num 是否大于5。如果条件成立,执行 then 后面的语句;否则,执行 else 后面的语句。fi 表示 if 语句的结束。

Bash还支持 elif 关键字,用于添加多个条件判断:

score=75
if [ $score -ge 90 ]; then
    echo "A"
elif [ $score -ge 80 ]; then
    echo "B"
elif [ $score -ge 70 ]; then
    echo "C"
else
    echo "D"
fi

循环语句

循环语句允许重复执行一段代码。Bash中有几种不同类型的循环,如 for 循环、while 循环和 until 循环。

for 循环常用于遍历列表或执行固定次数的操作。例如:

for i in 1 2 3 4 5; do
    echo "Number: $i"
done

上述代码会依次输出数字1到5。

while 循环则在条件为真时持续执行代码块。例如:

count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    count=$((count + 1))
done

这里 [ $count -le 5 ] 作为条件,只要 $count 小于等于5,就会一直执行循环体中的代码,并在每次循环中增加 $count 的值。

until 循环与 while 循环相反,它在条件为假时持续执行代码块。例如:

num=10
until [ $num -lt 5 ]; do
    echo "Number: $num"
    num=$((num - 1))
done

此代码中,只要 $num 大于等于5,就会执行循环体,每次循环 $num 减1,直到 $num 小于5时停止循环。

Docker 容器基础

Docker是一个开源的应用容器引擎,它允许开发者将应用及其依赖打包到一个可移植的容器中,然后发布到任何支持Docker的环境中运行。Docker容器提供了轻量级、隔离的运行环境,使得应用的部署和管理变得更加简单和高效。

Docker 架构

Docker采用客户端 - 服务器架构。Docker客户端(docker 命令行工具)与Docker守护进程(dockerd)进行通信,Docker守护进程负责构建、运行和管理Docker容器。Docker镜像存储在Docker仓库中,Docker客户端可以从仓库中拉取镜像并在本地运行容器。

Docker 镜像

Docker镜像是一个只读的模板,包含了运行一个应用所需的所有内容,如代码、运行时环境、库和系统工具等。镜像是分层构建的,每一层都代表了镜像构建过程中的一个步骤,这种分层结构使得镜像的构建和分发更加高效。

可以使用 docker build 命令基于一个 Dockerfile 来构建镜像。Dockerfile 是一个文本文件,包含了一系列用于构建镜像的指令。例如,下面是一个简单的 Dockerfile 用于构建一个基于Ubuntu的Python应用镜像:

FROM ubuntu:latest
RUN apt - get update && apt - get install - y python3 python3 - pip
COPY. /app
WORKDIR /app
RUN pip3 install - r requirements.txt
CMD ["python3", "app.py"]

在上述 Dockerfile 中:

  • FROM ubuntu:latest 指定了基础镜像为最新的Ubuntu镜像。
  • RUN 指令用于在镜像构建过程中执行命令,这里更新了Ubuntu系统并安装了Python3和pip。
  • COPY. /app 将当前目录下的所有文件复制到镜像中的 /app 目录。
  • WORKDIR /app 设置工作目录为 /app
  • RUN pip3 install - r requirements.txt 安装Python应用的依赖。
  • CMD ["python3", "app.py"] 指定容器启动时要执行的命令。

构建镜像的命令如下:

docker build -t my - python - app.

这里 -t 选项用于指定镜像的标签(名称和版本),最后的 . 表示当前目录,即 Dockerfile 所在的目录。

Docker 容器

Docker容器是基于Docker镜像创建的可运行实例。容器提供了一个隔离的运行环境,其中的应用与宿主机及其他容器相互隔离。可以使用 docker run 命令来运行容器。例如,运行上面构建的 my - python - app 镜像:

docker run - d - p 8080:8080 my - python - app

这里 -d 选项表示在后台运行容器,-p 8080:8080 将容器内部的8080端口映射到宿主机的8080端口,使得可以通过宿主机的8080端口访问容器内运行的应用。

Bash 脚本与 Docker 容器的结合

在实际应用中,常常需要使用Bash脚本来自动化管理Docker容器。这可以大大简化容器的部署、启动、停止和更新等操作。

使用 Bash 脚本构建 Docker 镜像

可以编写一个Bash脚本来自动化构建Docker镜像的过程。假设我们有一个Python项目,目录结构如下:

my_project/
├── app.py
├── requirements.txt
└── Dockerfile

下面是一个构建镜像的Bash脚本 build_image.sh

#!/bin/bash

IMAGE_NAME="my - python - app"
VERSION="1.0"

echo "Building Docker image $IMAGE_NAME:$VERSION"
docker build -t $IMAGE_NAME:$VERSION.

在这个脚本中,定义了镜像名称 IMAGE_NAME 和版本号 VERSION,然后使用 docker build 命令构建镜像,并为其添加了标签。

使用 Bash 脚本运行 Docker 容器

除了构建镜像,还可以编写Bash脚本来运行Docker容器。下面是一个启动容器的脚本 run_container.sh

#!/bin/bash

IMAGE_NAME="my - python - app"
VERSION="1.0"
CONTAINER_NAME="my - app - container"
PORT=8080

echo "Checking if container $CONTAINER_NAME is running..."
if docker ps - a | grep - q $CONTAINER_NAME; then
    echo "Container $CONTAINER_NAME is already running. Stopping it..."
    docker stop $CONTAINER_NAME
    docker rm $CONTAINER_NAME
fi

echo "Running container $CONTAINER_NAME from image $IMAGE_NAME:$VERSION"
docker run - d - p $PORT:$PORT --name $CONTAINER_NAME $IMAGE_NAME:$VERSION

在这个脚本中:

  • 首先检查名为 my - app - container 的容器是否正在运行,如果是,则停止并删除该容器。
  • 然后使用 docker run 命令从指定的镜像 my - python - app:1.0 运行一个新的容器,并将容器内部的8080端口映射到宿主机的8080端口,同时为容器命名为 my - app - container

使用 Bash 脚本更新 Docker 容器

在应用有更新时,需要更新Docker镜像并重启容器。下面是一个更新容器的脚本 update_container.sh

#!/bin/bash

IMAGE_NAME="my - python - app"
VERSION="1.0"
CONTAINER_NAME="my - app - container"
PORT=8080

echo "Pulling latest image $IMAGE_NAME:$VERSION"
docker pull $IMAGE_NAME:$VERSION

echo "Checking if container $CONTAINER_NAME is running..."
if docker ps - a | grep - q $CONTAINER_NAME; then
    echo "Container $CONTAINER_NAME is running. Stopping it..."
    docker stop $CONTAINER_NAME
    docker rm $CONTAINER_NAME
fi

echo "Running new container $CONTAINER_NAME from updated image $IMAGE_NAME:$VERSION"
docker run - d - p $PORT:$PORT --name $CONTAINER_NAME $IMAGE_NAME:$VERSION

这个脚本首先使用 docker pull 命令拉取最新的镜像,然后检查并停止正在运行的容器,最后使用更新后的镜像运行一个新的容器。

高级应用:使用 Bash 脚本管理多容器应用

在实际场景中,很多应用由多个相互关联的容器组成,例如一个Web应用可能包括前端容器、后端容器和数据库容器。使用Bash脚本可以方便地管理这些多容器应用。

假设我们有一个简单的Web应用,由一个前端(基于Node.js)和一个后端(基于Python Flask)以及一个MySQL数据库组成。目录结构如下:

multi - app/
├── frontend/
│   ├── app.js
│   ├── package.json
│   └── Dockerfile
├── backend/
│   ├── app.py
│   ├── requirements.txt
│   └── Dockerfile
└── scripts/
    ├── build_images.sh
    ├── run_containers.sh
    ├── stop_containers.sh

构建多容器应用的镜像

build_images.sh 脚本用于构建所有容器的镜像:

#!/bin/bash

FRONTEND_IMAGE="my - frontend"
BACKEND_IMAGE="my - backend"
DB_IMAGE="my - db"

echo "Building frontend image $FRONTEND_IMAGE"
cd frontend
docker build -t $FRONTEND_IMAGE.
cd..

echo "Building backend image $BACKEND_IMAGE"
cd backend
docker build -t $BACKEND_IMAGE.
cd..

echo "Building database image $DB_IMAGE"
cd db
docker build -t $DB_IMAGE.
cd..

这个脚本分别进入前端、后端和数据库目录,使用 docker build 命令构建各自的镜像。

运行多容器应用

run_containers.sh 脚本用于启动所有容器,并设置它们之间的网络连接:

#!/bin/bash

FRONTEND_IMAGE="my - frontend"
BACKEND_IMAGE="my - backend"
DB_IMAGE="my - db"

FRONTEND_CONTAINER="my - frontend - container"
BACKEND_CONTAINER="my - backend - container"
DB_CONTAINER="my - db - container"

echo "Creating network for containers"
docker network create my - app - network

echo "Running database container $DB_CONTAINER"
docker run - d --name $DB_CONTAINER --network my - app - network - e MYSQL_ROOT_PASSWORD=rootpassword - e MYSQL_DATABASE=mydb - e MYSQL_USER=myuser - e MYSQL_PASSWORD=mypassword $DB_IMAGE

echo "Running backend container $BACKEND_CONTAINER"
docker run - d --name $BACKEND_CONTAINER --network my - app - network - p 5000:5000 $BACKEND_IMAGE

echo "Running frontend container $FRONTEND_CONTAINER"
docker run - d --name $FRONTEND_CONTAINER --network my - app - network - p 3000:3000 $FRONTEND_IMAGE

在这个脚本中:

  • 首先创建了一个名为 my - app - network 的Docker网络,用于容器之间的通信。
  • 然后依次启动数据库容器、后端容器和前端容器,并将它们连接到同一个网络。同时,为数据库容器设置了一些环境变量来配置MySQL。

停止多容器应用

stop_containers.sh 脚本用于停止并删除所有容器以及相关的网络:

#!/bin/bash

FRONTEND_CONTAINER="my - frontend - container"
BACKEND_CONTAINER="my - backend - container"
DB_CONTAINER="my - db - container"

echo "Stopping and removing frontend container $FRONTEND_CONTAINER"
if docker ps - a | grep - q $FRONTEND_CONTAINER; then
    docker stop $FRONTEND_CONTAINER
    docker rm $FRONTEND_CONTAINER
fi

echo "Stopping and removing backend container $BACKEND_CONTAINER"
if docker ps - a | grep - q $BACKEND_CONTAINER; then
    docker stop $BACKEND_CONTAINER
    docker rm $BACKEND_CONTAINER
fi

echo "Stopping and removing database container $DB_CONTAINER"
if docker ps - a | grep - q $DB_CONTAINER; then
    docker stop $DB_CONTAINER
    docker rm $DB_CONTAINER
fi

echo "Removing network my - app - network"
docker network rm my - app - network

此脚本依次检查并停止、删除前端、后端和数据库容器,最后删除相关的Docker网络。

在容器内运行 Bash 脚本

有时候,需要在Docker容器内部运行Bash脚本,以完成一些容器内的自动化任务。例如,在一个基于Ubuntu的容器中,可能需要安装一些额外的软件包并配置环境。

假设我们有一个简单的Bash脚本 setup.sh 用于在容器内安装一些工具:

#!/bin/bash

echo "Updating package list..."
apt - get update

echo "Installing vim and curl..."
apt - get install - y vim curl

可以在 Dockerfile 中添加指令来将这个脚本复制到容器内并运行:

FROM ubuntu:latest

COPY setup.sh /tmp/
RUN chmod +x /tmp/setup.sh
RUN /tmp/setup.sh

CMD ["bash"]

在上述 Dockerfile 中:

  • COPY setup.sh /tmp/setup.sh 脚本复制到容器的 /tmp 目录。
  • RUN chmod +x /tmp/setup.sh 赋予脚本执行权限。
  • RUN /tmp/setup.sh 运行脚本,在容器内更新软件包列表并安装vim和curl。

当构建并运行基于这个 Dockerfile 的容器时,容器启动时会自动执行 setup.sh 脚本中的命令。

处理 Bash 脚本与 Docker 容器的错误

在使用Bash脚本管理Docker容器时,可能会遇到各种错误,如镜像构建失败、容器启动失败等。正确处理这些错误对于确保应用的稳定性和可靠性非常重要。

镜像构建错误处理

在构建镜像的Bash脚本中,可以通过检查 docker build 命令的返回值来判断镜像构建是否成功。例如:

#!/bin/bash

IMAGE_NAME="my - app - image"
VERSION="1.0"

echo "Building Docker image $IMAGE_NAME:$VERSION"
docker build -t $IMAGE_NAME:$VERSION.
if [ $? -ne 0 ]; then
    echo "Image build failed"
    exit 1
fi
echo "Image build successful"

在上述脚本中,$? 表示上一个命令的返回值。如果 docker build 命令执行成功,返回值为0;否则,返回值不为0。通过检查 $?,如果镜像构建失败,脚本会输出错误信息并退出,退出码为1。

容器运行错误处理

在运行容器的脚本中,同样可以检查 docker run 命令的返回值来判断容器是否成功启动。例如:

#!/bin/bash

IMAGE_NAME="my - app - image"
VERSION="1.0"
CONTAINER_NAME="my - app - container"
PORT=8080

echo "Running container $CONTAINER_NAME from image $IMAGE_NAME:$VERSION"
docker run - d - p $PORT:$PORT --name $CONTAINER_NAME $IMAGE_NAME:$VERSION
if [ $? -ne 0 ]; then
    echo "Container run failed"
    exit 1
fi
echo "Container run successful"

这里如果 docker run 命令执行失败,脚本会输出错误信息并退出,退出码为1。

此外,还可以通过检查容器的运行状态来判断容器是否正常运行。例如:

#!/bin/bash

CONTAINER_NAME="my - app - container"

echo "Checking if container $CONTAINER_NAME is running..."
if docker ps | grep - q $CONTAINER_NAME; then
    echo "Container $CONTAINER_NAME is running"
else
    echo "Container $CONTAINER_NAME is not running"
    exit 1
fi

这个脚本通过 docker ps 命令检查指定名称的容器是否在运行列表中,如果不在,则输出错误信息并退出。

优化 Bash 脚本与 Docker 容器的性能

为了提高Bash脚本管理Docker容器的效率以及Docker容器本身的性能,可以采取一些优化措施。

优化 Bash 脚本

  1. 减少不必要的命令执行:在Bash脚本中,尽量避免重复执行相同的命令。例如,如果需要多次检查容器是否运行,可以将检查结果存储在变量中,而不是每次都执行 docker ps 命令。
CONTAINER_NAME="my - app - container"
is_running=$(docker ps | grep - q $CONTAINER_NAME && echo "true" || echo "false")
if [ "$is_running" == "true" ]; then
    echo "Container is running"
else
    echo "Container is not running"
fi
  1. 使用函数:将重复的代码块封装成函数,提高脚本的可读性和可维护性。例如,在管理多容器应用的脚本中,可以将停止和删除容器的操作封装成函数:
stop_and_remove_container() {
    local container_name=$1
    echo "Stopping and removing container $container_name"
    if docker ps - a | grep - q $container_name; then
        docker stop $container_name
        docker rm $container_name
    fi
}

FRONTEND_CONTAINER="my - frontend - container"
BACKEND_CONTAINER="my - backend - container"
DB_CONTAINER="my - db - container"

stop_and_remove_container $FRONTEND_CONTAINER
stop_and_remove_container $BACKEND_CONTAINER
stop_and_remove_container $DB_CONTAINER

优化 Docker 容器

  1. 选择合适的基础镜像:尽量选择官方提供的、体积较小的基础镜像,以减少镜像的大小和下载时间。例如,对于基于Alpine Linux的镜像,其体积通常比基于Ubuntu或Debian的镜像小很多。
FROM alpine:latest
# 后续指令
  1. 优化镜像构建:在 Dockerfile 中,尽量合并 RUN 指令,减少镜像的层数。例如:
# 不好的做法
RUN apt - get update
RUN apt - get install - y package1
RUN apt - get install - y package2

# 好的做法
RUN apt - get update && apt - get install - y package1 package2
  1. 资源限制与分配:在运行容器时,可以根据应用的需求合理分配CPU和内存资源,避免资源浪费或过度使用导致性能问题。例如:
docker run - d --name my - container - m 512m --cpus="0.5" my - image

这里 -m 512m 限制容器使用512MB内存,--cpus="0.5" 表示容器最多使用半个CPU核心。

安全性考虑

在使用Bash脚本管理Docker容器时,安全性是一个至关重要的问题。以下是一些需要注意的安全要点:

Bash 脚本安全

  1. 权限管理:确保Bash脚本具有适当的权限,避免脚本具有过高的权限导致安全风险。只赋予脚本执行所需的最小权限。
  2. 输入验证:如果脚本接受用户输入,一定要对输入进行严格的验证,防止恶意输入导致命令注入攻击。例如,在一个接受容器名称作为参数的脚本中:
#!/bin/bash

if [ $# -ne 1 ]; then
    echo "Usage: $0 <container_name>"
    exit 1
fi

container_name=$1
if [[! $container_name =~ ^[a - zA - Z0 - 9_.-]+$ ]]; then
    echo "Invalid container name"
    exit 1
fi

docker stop $container_name

在上述脚本中,首先检查是否提供了一个参数,然后使用正则表达式验证参数是否为合法的容器名称,防止用户输入恶意命令。

Docker 容器安全

  1. 镜像安全:从可信的源拉取镜像,避免使用来源不明的镜像,因为这些镜像可能包含恶意软件或漏洞。定期更新镜像,以获取安全补丁。
  2. 容器隔离:利用Docker的隔离机制,确保容器之间以及容器与宿主机之间的隔离。避免在容器内以root用户运行应用,尽量使用非root用户。可以在 Dockerfile 中创建一个非root用户并切换到该用户:
FROM ubuntu:latest

RUN useradd -m -s /bin/bash myuser
RUN chown -R myuser:myuser /app
USER myuser

CMD ["python3", "app.py"]
  1. 网络安全:在配置容器网络时,只暴露必要的端口,避免开放不必要的网络端口导致安全风险。可以使用Docker网络策略来限制容器之间的网络访问。例如:
docker network create --driver bridge --subnet 172.18.0.0/16 my - network
docker network connect --ip 172.18.0.2 my - network my - container - 1
docker network connect --ip 172.18.0.3 my - network my - container - 2

# 创建网络策略,只允许container - 1访问container - 2的特定端口
docker network connect --ip 172.18.0.2 my - network my - container - 1
docker network connect --ip 172.18.0.3 my - network my - container - 2
docker network connect --ip 172.18.0.4 my - network my - container - 3

docker network policy create my - network - policy \
    --scope my - network \
    --ingress - rule="my - container - 1:172.18.0.2->my - container - 2:172.18.0.3:8080"

通过上述网络策略,只有 my - container - 1 可以访问 my - container - 2 的8080端口,其他容器之间的访问被限制。

通过以上对Bash脚本与Docker容器的深入探讨,我们了解了它们各自的基础知识、如何结合使用以及在实际应用中的各种优化和安全考虑。熟练掌握这些内容将有助于我们更高效、安全地进行应用的部署和管理。