前置要求

  1. ubuntu server 22.04 LTS 安装与配置

  2. centos7.9 安装与配置

1. 脚本概述

  1. 安装时会将所有二进制文件通过循环创建软链接的方式放到 /usr/bin 目录下,卸载时也会一并删除无侵入式安装。

  2. 获取版本时已经将 20.x 以下的版本给过滤掉了,往下的版本实在是太老了不建议再安装了。

  3. 请注意,卸载的时候会删除所有 docker 数据,请斟酌查看脚本并修改是否保留数据,注释掉删除语句即可。

  • DOCKER_INSTALL_DIR:Docker 的安装目录(默认为 /app/docker)。

  • DOCKER_USR_BIN:Docker 二进制文件的软链接路径(默认为 /usr/bin)。

  • DOCKER_URL:Docker 二进制安装包的下载路径(https://download.docker.com/linux/static/stable/x86_64/)。

  • DOCKER_DATA_DIR:Docker 数据目录(默认为 /data/docker)。

  • DOCKER_DOWNLOAD_DIR:临时目录用于存放下载的 Docker 二进制安装包(默认为 /tmp/docker_install)。

#!/bin/bash
# Docker 安装目录和相关路径
DOCKER_INSTALL_DIR="/app/docker"
DOCKER_USR_BIN="/usr/bin"
DOCKER_URL="https://download.docker.com/linux/static/stable/x86_64/"
DOCKER_DATA_DIR="/data/docker"
DOCKER_DOWNLOAD_DIR="/tmp/docker_install"

# 创建临时目录
mkdir -p "$DOCKER_DOWNLOAD_DIR"

print_separator() {
    printf '%*s\n' "${COLUMNS:-80}" '' | tr ' ' -
}

# 检查系统中是否已有 Docker 安装,并输出当前版本
check_existing_docker() {
    if command -v docker &> /dev/null; then
        current_version=$(docker --version | grep -oP 'Docker version \K[0-9]+\.[0-9]+\.[0-9]+')
        echo ""
        echo "检测到系统中已安装 Docker 版本: $current_version"
        echo "为了确保安装正确的版本,请先卸载当前 Docker。"
        echo "建议使用卸载选项来清除现有安装,然后重新运行该脚本进行安装。"
        echo ""
        exit 1  # 停止脚本运行
    else
        echo ""
        echo "未检测到系统中已安装 Docker,可以继续安装。"
    fi
}

# 获取可用的 Docker 版本(联网)
list_docker_versions() {
    echo "正在获取可用的 Docker 版本..."

    # 使用 curl 获取页面内容,并从中提取版本号,只保留 20.x.x 及以上的版本
    page_content=$(curl -s "$DOCKER_URL")
    versions=$(echo "$page_content" | grep -oP 'docker-\K[2-9][0-9]+\.[0-9]+\.[0-9]+' | sort -rV | uniq)

    if [[ -z "$versions" ]]; then
        echo "未找到 Docker 版本。请检查您的网络连接或 Docker URL。"
    else
        echo "可用的 Docker 版本:"
        echo "$versions"
    fi
}


# 列出本地已下载的 Docker 版本
list_local_docker_versions() {
    echo "已下载的 Docker 版本:"

    local_versions=$(ls "$DOCKER_DOWNLOAD_DIR" | grep -oP 'docker-\K[0-9]+\.[0-9]+\.[0-9]+(?=\.tgz)' | sort -rV | uniq)

    if [[ -z "$local_versions" ]]; then
        echo "未找到本地 Docker 安装包。请先使用联网安装功能下载。"
    else
        echo "$local_versions"
    fi
}

# 下载 Docker 到本地
download_docker() {
    version=$1
    echo "正在下载 Docker 版本 $version..."
    
    # 下载并检查返回的 HTTP 状态码
    curl -L "${DOCKER_URL}docker-$version.tgz" -o "$DOCKER_DOWNLOAD_DIR/docker-$version.tgz"
    
    if [[ $? -ne 0 ]]; then
        echo "错误: 下载 Docker 版本 $version 失败。请检查网络连接或版本号是否正确。"
        exit 1  # 退出脚本
    fi
}

# 检查版本号是否在列表中(通用函数)
check_version_exists() {
    version=$1
    available_versions=$2

    if [[ "$available_versions" =~ "$version" ]]; then
        return 0  # 版本存在
    else
        return 1  # 版本不存在
    fi
}

# 安装 Docker
install_docker() {
    version=$1

    # 解压并移动到目标安装目录
    echo "正在将 Docker 版本 $version 安装到 $DOCKER_INSTALL_DIR..."
    mkdir -p "$DOCKER_INSTALL_DIR"
    mkdir -p "$DOCKER_DATA_DIR"
    
    # 解压并检查解压是否成功
    tar -xzf "$DOCKER_DOWNLOAD_DIR/docker-$version.tgz" -C "$DOCKER_INSTALL_DIR" --strip-components=1
    if [[ $? -ne 0 ]]; then
        echo "错误: 解压 Docker 版本 $version 时出错。文件可能已损坏或格式不正确。"
        exit 1  # 退出脚本
    fi

    # 创建软连接,将 docker 目录下的所有文件链接到 /usr/bin
    echo "正在为 Docker 二进制文件创建软链接..."
    for bin_file in "$DOCKER_INSTALL_DIR"/*; do
        if [ -f "$bin_file" ]; then
            filename=$(basename "$bin_file")
            ln -sf "$bin_file" "$DOCKER_USR_BIN/$filename"
        fi
    done

    echo "Docker $version 安装成功。"

    # 创建docker组,给docker.service使用
    groupadd -g 999 docker

    # 创建 docker.service 文件
    create_docker_service_file

    # 设置 Docker 数据目录权限
    chmod -R 755 "$DOCKER_DATA_DIR"

    # 刷新 systemd 配置并启动 Docker
    echo "正在重新加载 systemd 并启动 Docker 服务..."
    systemctl daemon-reload
    systemctl start docker
    systemctl enable docker

    if [[ $? -ne 0 ]]; then
        echo "错误: Docker 服务启动失败。请查看日志了解详细信息。"
        exit 1
    fi

    echo "Docker 服务已启动并设置为开机自启。"

    # 调用分隔符
    print_separator
    # 添加 Docker 版本信息输出
    docker_version_output=$(docker version)
    echo "$docker_version_output"
}

# 创建 Docker 服务文件 /usr/lib/systemd/system/docker.service
create_docker_service_file() {
    echo "正在创建 /usr/lib/systemd/system/docker.service 文件..."
    cat << EOF | sudo tee /usr/lib/systemd/system/docker.service > /dev/null

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
# After=network-online.target firewalld.service containerd.service
# Wants=network-online.target
# Requires=docker.socket containerd.service

[Service]
# 代理
# Environment="HTTP_PROXY=http://172.31.0.1:7890/"
# Environment="HTTPS_PROXY=http://172.31.0.1:7890/"
# Environment="NO_PROXY=localhost,10.*,172.*,192.*"
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd --data-root=$DOCKER_DATA_DIR --group=docker
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

# Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229.
# Both the old, and new location are accepted by systemd 229 and up, so using the old location
# to make them work for either version of systemd.
StartLimitBurst=3

# Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230.
# Both the old, and new name are accepted by systemd 230 and up, so using the old name to make
# this option work for either version of systemd.
StartLimitInterval=60s

# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

# Comment TasksMax if your systemd version does not support it.
# Only systemd 226 and above support this option.
TasksMax=infinity

# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes

# kill only the docker process, not all processes in the cgroup
KillMode=process
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target

EOF
    echo "docker.service 文件创建成功。"
}

# 卸载 Docker
uninstall_docker() {
    echo "正在卸载 Docker..."

    # 停止 Docker 服务
    systemctl stop docker

    # 删除软链接
    echo "正在删除 Docker 二进制文件的软链接..."
    for bin_file in "$DOCKER_INSTALL_DIR"/*; do
        if [ -f "$bin_file" ]; then
            filename=$(basename "$bin_file")
            rm -f "$DOCKER_USR_BIN/$filename"
        fi
    done

    # 兼容旧版的删除
    rm -rf /usr/bin/containerd*
    rm -rf /usr/bin/containerd-shim-runc-v2*
    rm -rf /usr/bin/ctr*
    rm -rf /usr/bin/docker*
    rm -rf /usr/bin/dockerd*
    rm -rf /usr/bin/docker-init*

    # 删除安装目录
    rm -rf "$DOCKER_INSTALL_DIR"
    # 删除数据目录
    rm -rf "$DOCKER_DATA_DIR"

    # 删除 Docker 数据目录软链接(如果有的话)
    rm -rf /var/lib/docker
    rm -rf /var/run/docker
    rm -rf /etc/docker
    rm -rf /usr/lib/systemd/system/docker.service
    rm -rf /lib/systemd/system/docker.service
    rm -rf /etc/systemd/system/docker.service

    # 删除docker组
    sudo groupdel docker


    systemctl daemon-reload

    echo "Docker 卸载成功。"
}

# 脚本主逻辑
main() {
    echo "Docker 安装脚本"
    echo "1) 列出可用的 Docker 版本(联网)"
    echo "2) 安装 Docker(联网)"
    echo "3) 安装 Docker(本地)"
    echo "4) 卸载 Docker"
    echo "5) 退出"

    read -p "请选择一个选项 [1-5]: " option

    case $option in
        1)
            list_docker_versions
            ;;
        2)
            list_docker_versions
            read -p "请输入要安装的 Docker 版本: " version
            available_versions=$(list_docker_versions)
            if check_version_exists "$version" "$available_versions"; then
                download_docker "$version"

                # 检查是否已安装 Docker
                check_existing_docker

                install_docker "$version"
            else
                echo "错误: 输入的版本号 $version 不存在于可用版本列表中。"
            fi
            ;;
        3)           
            # 检查是否已安装 Docker
            check_existing_docker

            list_local_docker_versions
            read -p "请输入要安装的本地 Docker 版本: " version
            local_versions=$(list_local_docker_versions)
            if check_version_exists "$version" "$local_versions"; then
                install_docker "$version"
            else
                echo "错误: 输入的版本号 $version 不存在于本地已下载的版本列表中。"
            fi
            ;;
        4)
            uninstall_docker
            ;;
        5)
            exit 0
            ;;
        *)
            echo "无效选项。"
            ;;
    esac
}

# 运行主函数
main

2. 使用步骤

2.1 列出可用的 Docker 版本(联网)

[root@localhost shell]# ./docker-install.sh
Docker 安装脚本
1) 列出可用的 Docker 版本(联网)
2) 安装 Docker(联网)
3) 安装 Docker(本地)
4) 卸载 Docker
5) 退出
请选择一个选项 [1-5]: 1
正在获取可用的 Docker 版本...
可用的 Docker 版本:
27.2.1
27.2.0
27.1.2
27.1.1
..............忽略输出

2.2 安装 Docker(联网)

[root@localhost shell]# ./docker-install.sh
Docker 安装脚本
1) 列出可用的 Docker 版本(联网)
2) 安装 Docker(联网)
3) 安装 Docker(本地)
4) 卸载 Docker
5) 退出
请选择一个选项 [1-5]: 2
正在获取可用的 Docker 版本...
可用的 Docker 版本:
27.2.1
27.2.0
24.0.9
20.10.1
20.10.0
..............忽略输出

请输入要安装的 Docker 版本: 24.0.9
正在下载 Docker 版本 24.0.9...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 66.9M  100 66.9M    0     0  13.3M      0  0:00:04  0:00:04 --:--:-- 15.2M

未检测到系统中已安装 Docker,可以继续安装。
正在将 Docker 版本 24.0.9 安装到 /app/docker...
正在为 Docker 二进制文件创建软链接...
Docker 24.0.9 安装成功。
正在创建 /usr/lib/systemd/system/docker.service 文件...
docker.service 文件创建成功。
正在重新加载 systemd 并启动 Docker 服务...
Docker 服务已启动并设置为开机自启。
--------------------------------------------------------------------------------
Client:
 Version:           24.0.9
 API version:       1.43
 Go version:        go1.20.13
 Git commit:        2936816
 Built:             Thu Feb  1 00:47:46 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          24.0.9
  API version:      1.43 (minimum version 1.12)
  Go version:       go1.20.13
  Git commit:       fca702d
  Built:            Thu Feb  1 00:49:16 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.7.13
  GitCommit:        7c3aca7a610df76212171d200ca3811ff6096eb8
 runc:
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

2.3 安装 Docker(本地)

由于网络限制导致 Docker 在线安装困难,我已为你准备了几个较新的 Docker 版本。你可以将它们下载放置到 /tmp/docker_install 目录中,脚本会自动识别并从本地安装这些版本。

docker-27.2.1.tgz

docker-26.1.4.tgz

docker-25.0.5.tgz

docker-24.0.9.tgz

docker-23.0.6.tgz

docker-20.10.24.tgz

[root@localhost shell]# ./docker-install.sh
Docker 安装脚本
1) 列出可用的 Docker 版本(联网)
2) 安装 Docker(联网)
3) 安装 Docker(本地)
4) 卸载 Docker
5) 退出
请选择一个选项 [1-5]: 3

未检测到系统中已安装 Docker,可以继续安装。
已下载的 Docker 版本:
24.0.9
23.0.6
20.10.24
请输入要安装的本地 Docker 版本: 20.10.24
正在将 Docker 版本 20.10.24 安装到 /app/docker...
正在为 Docker 二进制文件创建软链接...
Docker 20.10.24 安装成功。
正在创建 /usr/lib/systemd/system/docker.service 文件...
docker.service 文件创建成功。
正在重新加载 systemd 并启动 Docker 服务...
Docker 服务已启动并设置为开机自启。
--------------------------------------------------------------------------------
Client:
 Version:           20.10.24
 API version:       1.41
 Go version:        go1.19.7
 Git commit:        297e128
 Built:             Tue Apr  4 18:17:06 2023
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.24
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.19.7
  Git commit:       5d6db84
  Built:            Tue Apr  4 18:23:02 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.6.20
  GitCommit:        2806fc1057397dbaeefbea0e4e17bddfbd388f38
 runc:
  Version:          1.1.5
  GitCommit:        v1.1.5-0-gf19387a6
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

2.4 卸载 Docker

[root@localhost shell]# ./docker-install.sh
Docker 安装脚本
1) 列出可用的 Docker 版本(联网)
2) 安装 Docker(联网)
3) 安装 Docker(本地)
4) 卸载 Docker
5) 退出
请选择一个选项 [1-5]: 4
正在卸载 Docker...
正在删除 Docker 二进制文件的软链接...
Docker 卸载成功。

3. 后续拓展

  1. 根据配置文件自定义一键多节点安装

  2. 访问docker网站失败的情况下预准备安装包方式部署

4. 写在最后

是不是很神奇哈哈一个脚本完成很实用的功能,实测在ubuntu和centos是能够正常运行的,至于其他系统我还没有测试过。欢迎给我留言好用我会继续拓展的。