391 lines
13 KiB
Bash
391 lines
13 KiB
Bash
#!/bin/bash
|
|
# ============================================================
|
|
# Docker 一键安装脚本
|
|
# 适用平台: Ubuntu 22.04 (ARM64 / aarch64 / x86_64)
|
|
# 说明: 通过 Ubuntu 官方 ports 源安装 docker.io
|
|
# 不修改 eth0 网络配置, 不需要重启
|
|
# ============================================================
|
|
|
|
set -e
|
|
|
|
# ---------- 颜色输出 ----------
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
die() { error "$*"; exit 1; }
|
|
|
|
echo ""
|
|
echo "============================================================"
|
|
echo " Docker 一键安装脚本 (Ubuntu 22.04 ARM64/x86_64)"
|
|
echo "============================================================"
|
|
echo ""
|
|
|
|
# ---------- 验证函数 (必须在调用前定义) ----------
|
|
verify_docker() {
|
|
echo ""
|
|
info "验证 Docker 安装..."
|
|
|
|
if ! command -v docker &>/dev/null; then
|
|
die "docker 命令不存在, 安装可能失败"
|
|
fi
|
|
DOCKER_VER=$(docker --version)
|
|
success "Docker 版本: $DOCKER_VER"
|
|
|
|
if pgrep -x dockerd &>/dev/null; then
|
|
success "dockerd 进程运行中 ✓"
|
|
else
|
|
warn "dockerd 进程未检测到, Docker 可能未正常启动"
|
|
fi
|
|
|
|
if [ -S /var/run/docker.sock ]; then
|
|
success "Docker socket 存在: /var/run/docker.sock ✓"
|
|
else
|
|
warn "Docker socket 不存在, 服务可能未启动"
|
|
fi
|
|
|
|
if docker info &>/dev/null; then
|
|
success "docker info 执行成功 ✓"
|
|
else
|
|
warn "docker info 失败, 请检查 Docker 服务状态"
|
|
info "可尝试: systemctl status docker 或 journalctl -u docker"
|
|
fi
|
|
|
|
echo ""
|
|
info "验证核心命令可用性..."
|
|
for cmd in "docker pull --help" "docker push --help" "docker commit --help" \
|
|
"docker export --help" "docker load --help" "docker images" \
|
|
"docker ps"; do
|
|
if $cmd &>/dev/null; then
|
|
success " $cmd ✓"
|
|
else
|
|
warn " $cmd 执行异常"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# ---------- 1. 检查架构 ----------
|
|
info "检查系统架构..."
|
|
ARCH=$(uname -m)
|
|
if [ "$ARCH" = "aarch64" ]; then
|
|
success "架构: aarch64 (ARM64) ✓"
|
|
elif [ "$ARCH" = "x86_64" ]; then
|
|
success "架构: x86_64 ✓"
|
|
else
|
|
warn "当前架构为 $ARCH, 本脚本针对 aarch64/x86_64 优化, 继续执行但可能出现问题"
|
|
fi
|
|
|
|
# ---------- 2. 检查是否 root ----------
|
|
info "检查运行权限..."
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
die "请使用 root 权限运行本脚本: sudo bash $0"
|
|
fi
|
|
success "当前为 root 用户 ✓"
|
|
|
|
# ---------- 3. 保护 eth0 ----------
|
|
info "检查 eth0 网络接口..."
|
|
if ! ip link show eth0 &>/dev/null; then
|
|
warn "eth0 接口不存在, 跳过保护检查"
|
|
else
|
|
ETH0_STATE=$(ip link show eth0 | grep -o 'state [A-Z]*' | awk '{print $2}')
|
|
ETH0_IP=$(ip addr show eth0 | grep 'inet ' | awk '{print $2}' | head -1)
|
|
success "eth0 状态: $ETH0_STATE, IP: ${ETH0_IP:-未分配}"
|
|
info "注意: 本脚本不会修改 eth0 的任何配置"
|
|
fi
|
|
|
|
# ---------- 4. 检查 Docker 是否已安装 ----------
|
|
info "检查 Docker 是否已安装..."
|
|
if command -v docker &>/dev/null; then
|
|
DOCKER_VER=$(docker --version 2>/dev/null)
|
|
warn "Docker 已安装: $DOCKER_VER"
|
|
read -r -p "是否重新安装/覆盖? [y/N] " REINSTALL
|
|
if [[ ! "$REINSTALL" =~ ^[Yy]$ ]]; then
|
|
info "跳过安装, 直接验证现有 Docker..."
|
|
verify_docker
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# ---------- 5. 检查必要命令 ----------
|
|
info "检查依赖命令..."
|
|
MISSING_CMDS=()
|
|
for cmd in apt curl wget gpg; do
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
MISSING_CMDS+=("$cmd")
|
|
fi
|
|
done
|
|
|
|
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
|
|
warn "以下命令缺失: ${MISSING_CMDS[*]}, 尝试安装..."
|
|
apt-get update -qq && apt-get install -y "${MISSING_CMDS[@]}" \
|
|
|| die "无法安装缺失命令: ${MISSING_CMDS[*]}, 请手动安装后重试"
|
|
fi
|
|
success "依赖命令检查完毕 ✓"
|
|
|
|
# ---------- 6. 检查网络连通性 ----------
|
|
info "检查网络连通性..."
|
|
NETWORK_OK=false
|
|
|
|
if curl -s --connect-timeout 8 http://ports.ubuntu.com/ &>/dev/null; then
|
|
success "可访问 Ubuntu ports 源 (ports.ubuntu.com) ✓"
|
|
NETWORK_OK=true
|
|
APT_SOURCE="ubuntu_ports"
|
|
elif curl -s --connect-timeout 8 https://mirrors.tuna.tsinghua.edu.cn/ &>/dev/null; then
|
|
success "可访问清华大学镜像源 ✓"
|
|
NETWORK_OK=true
|
|
APT_SOURCE="tsinghua"
|
|
elif curl -s --connect-timeout 8 https://mirrors.ustc.edu.cn/ &>/dev/null; then
|
|
success "可访问中科大镜像源 ✓"
|
|
NETWORK_OK=true
|
|
APT_SOURCE="ustc"
|
|
else
|
|
error "无法访问任何已知软件源!"
|
|
echo ""
|
|
echo " 请检查以下内容:"
|
|
echo " 1. 网络是否正常: ping 114.114.114.114"
|
|
echo " 2. DNS 是否正常: nslookup ports.ubuntu.com"
|
|
echo " 3. 防火墙是否拦截出站流量"
|
|
echo ""
|
|
die "网络不可用, 无法继续安装"
|
|
fi
|
|
|
|
# ---------- 7. 更新 apt 缓存 ----------
|
|
info "更新 apt 软件包缓存..."
|
|
if ! apt-get update -qq 2>&1; then
|
|
warn "apt update 出现警告, 尝试忽略错误继续..."
|
|
apt-get update --allow-releaseinfo-change -qq 2>&1 || true
|
|
fi
|
|
success "apt 缓存更新完毕 ✓"
|
|
|
|
# ---------- 8. 安装依赖包 ----------
|
|
info "安装必要依赖包..."
|
|
DEPS=(ca-certificates curl gnupg lsb-release apt-transport-https)
|
|
MISSING_DEPS=()
|
|
for pkg in "${DEPS[@]}"; do
|
|
if ! dpkg -l "$pkg" 2>/dev/null | grep -q '^ii'; then
|
|
MISSING_DEPS+=("$pkg")
|
|
fi
|
|
done
|
|
|
|
if [ ${#MISSING_DEPS[@]} -gt 0 ]; then
|
|
info "安装缺失依赖: ${MISSING_DEPS[*]}"
|
|
apt-get install -y "${MISSING_DEPS[@]}" \
|
|
|| die "依赖包安装失败, 请检查网络或 apt 源配置"
|
|
fi
|
|
success "依赖包就绪 ✓"
|
|
|
|
# ---------- 9. 安装 Docker ----------
|
|
info "开始安装 Docker (docker.io)..."
|
|
|
|
apt-get install -y \
|
|
docker.io \
|
|
docker-buildx \
|
|
docker-compose-v2 \
|
|
|| die "Docker 安装失败! 请检查网络连接和 apt 源配置"
|
|
|
|
success "Docker 安装完毕 ✓"
|
|
|
|
# ---------- 10. 修复 iptables 后端 ----------
|
|
# 部分嵌入式/定制内核 (如 RT 内核) 不支持 nf_tables,
|
|
# 需切换到 iptables-legacy, 否则 dockerd 启动时报:
|
|
# "iptables (nf_tables): Could not fetch rule set generation id: Invalid argument"
|
|
info "检查 iptables 后端兼容性..."
|
|
|
|
# 若 iptables-legacy 不存在则先安装
|
|
if ! command -v iptables-legacy &>/dev/null && ! [ -f /usr/sbin/iptables-legacy ]; then
|
|
info "iptables-legacy 不存在, 尝试安装 iptables..."
|
|
apt-get install -y iptables 2>/dev/null || true
|
|
fi
|
|
|
|
if update-alternatives --list iptables 2>/dev/null | grep -q legacy; then
|
|
update-alternatives --set iptables /usr/sbin/iptables-legacy 2>/dev/null || true
|
|
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy 2>/dev/null || true
|
|
success "已切换到 iptables-legacy 后端 ✓"
|
|
else
|
|
warn "未找到 iptables-legacy 备选项, 若 Docker 启动失败请手动执行:"
|
|
warn " apt-get install -y iptables && update-alternatives --set iptables /usr/sbin/iptables-legacy"
|
|
fi
|
|
|
|
# ---------- 11. 配置 Docker 镜像加速 ----------
|
|
info "配置 Docker 镜像加速源..."
|
|
mkdir -p /etc/docker
|
|
|
|
# 检查内核是否有 iptable_raw 模块
|
|
# 部分嵌入式/定制内核 (如 RT 内核) 没有编译 iptable_raw,
|
|
# Docker 28.x 默认会用 raw 表做 DIRECT ACCESS FILTERING,
|
|
# 若模块不存在则 docker run 报错:
|
|
# "can't initialize iptables table `raw': Table does not exist"
|
|
# 解决方案: 在 daemon.json 加 "allow-direct-routing": true 跳过该规则
|
|
ALLOW_DIRECT_ROUTING="false"
|
|
if ! modprobe iptable_raw 2>/dev/null && ! lsmod | grep -q '^iptable_raw'; then
|
|
warn "内核缺少 iptable_raw 模块, 将启用 allow-direct-routing 绕过 raw 表限制"
|
|
ALLOW_DIRECT_ROUTING="true"
|
|
else
|
|
success "iptable_raw 模块可用 ✓"
|
|
fi
|
|
|
|
cat > /etc/docker/daemon.json <<EOF
|
|
{
|
|
"registry-mirrors": [
|
|
"https://docker.m.daocloud.io"
|
|
],
|
|
"dns": ["114.114.114.114", "223.5.5.5"],
|
|
"allow-direct-routing": ${ALLOW_DIRECT_ROUTING},
|
|
"log-driver": "json-file",
|
|
"log-opts": {
|
|
"max-size": "10m",
|
|
"max-file": "3"
|
|
}
|
|
}
|
|
EOF
|
|
success "镜像加速配置完毕 ✓"
|
|
|
|
# ---------- 12. 启动并设置开机自启 ----------
|
|
info "启动 Docker 服务..."
|
|
|
|
# 注意: 嵌入式板子上 systemd 可能处于 degraded 状态 (非关键服务失败),
|
|
# 不能用 is-system-running 判断, 改为直接检查 systemctl 命令是否可用
|
|
# 以及 docker.service 单元文件是否存在
|
|
SYSTEMD_OK=false
|
|
if command -v systemctl &>/dev/null && systemctl cat docker.service &>/dev/null 2>&1; then
|
|
SYSTEMD_OK=true
|
|
fi
|
|
|
|
if [ "$SYSTEMD_OK" = true ]; then
|
|
systemctl enable docker 2>/dev/null || warn "无法设置 docker 开机自启"
|
|
systemctl restart docker 2>/dev/null || warn "systemctl restart docker 失败, 尝试直接启动"
|
|
sleep 3
|
|
if systemctl is-active --quiet docker; then
|
|
success "Docker 服务已通过 systemd 启动 ✓"
|
|
else
|
|
warn "systemd 启动失败, 查看日志: journalctl -u docker -n 20"
|
|
warn "尝试直接启动 dockerd..."
|
|
pkill -x dockerd 2>/dev/null || true
|
|
sleep 1
|
|
dockerd --host=unix:///var/run/docker.sock >> /var/log/dockerd.log 2>&1 &
|
|
sleep 4
|
|
if pgrep -x dockerd &>/dev/null; then
|
|
success "dockerd 直接启动成功 ✓"
|
|
else
|
|
error "dockerd 启动失败, 请查看日志: /var/log/dockerd.log"
|
|
fi
|
|
fi
|
|
else
|
|
warn "systemd 不可用, 直接启动 dockerd..."
|
|
if ! pgrep -x dockerd &>/dev/null; then
|
|
dockerd --host=unix:///var/run/docker.sock >> /var/log/dockerd.log 2>&1 &
|
|
sleep 4
|
|
fi
|
|
if pgrep -x dockerd &>/dev/null; then
|
|
success "dockerd 直接启动成功 ✓"
|
|
else
|
|
error "dockerd 启动失败, 请查看日志: /var/log/dockerd.log"
|
|
fi
|
|
fi
|
|
|
|
# ---------- 13. 修复 DNS 解析 (内网DNS无法解析公网域名时的兜底方案) ----------
|
|
info "检查 Docker Hub 及镜像源 DNS 解析..."
|
|
|
|
DNS_FIXED=false
|
|
fix_dns_with_hosts() {
|
|
local dns_server="$1"
|
|
python3 -c "
|
|
import socket, sys
|
|
|
|
def dns_query(server, domain):
|
|
try:
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
s.settimeout(3)
|
|
tid = b'\xab\xcd'
|
|
flags = b'\x01\x00'
|
|
qdcount = b'\x00\x01'
|
|
header = tid + flags + qdcount + b'\x00\x00\x00\x00\x00\x00'
|
|
qname = b''
|
|
for part in domain.split('.'):
|
|
qname += bytes([len(part)]) + part.encode()
|
|
qname += b'\x00'
|
|
question = qname + b'\x00\x01\x00\x01'
|
|
s.sendto(header + question, (server, 53))
|
|
data, _ = s.recvfrom(512)
|
|
ips = []
|
|
i = 12
|
|
while data[i] != 0: i += data[i] + 1
|
|
i += 5
|
|
while i < len(data) - 10:
|
|
if data[i] >= 0xc0: i += 2
|
|
else:
|
|
while i < len(data) and data[i] != 0: i += data[i] + 1
|
|
i += 1
|
|
rtype = (data[i]<<8)|data[i+1]
|
|
rdlen = (data[i+8]<<8)|data[i+9]
|
|
i += 10
|
|
if rtype == 1 and rdlen == 4:
|
|
ips.append('.'.join(str(b) for b in data[i:i+4]))
|
|
i += rdlen
|
|
return ips
|
|
except: return []
|
|
finally: s.close()
|
|
|
|
server = sys.argv[1]
|
|
domains = [
|
|
'docker.m.daocloud.io',
|
|
'registry-1.docker.io',
|
|
'auth.docker.io',
|
|
]
|
|
results = []
|
|
for d in domains:
|
|
ips = dns_query(server, d)
|
|
if ips:
|
|
results.append(f'{ips[0]} {d}')
|
|
if results:
|
|
print('\n'.join(results))
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|
|
" "$dns_server" 2>/dev/null
|
|
}
|
|
|
|
HOSTS_BLOCK=""
|
|
for dns in 114.114.114.114 223.5.5.5; do
|
|
HOSTS_BLOCK=$(fix_dns_with_hosts "$dns" 2>/dev/null) && DNS_FIXED=true && break
|
|
done
|
|
|
|
if [ "$DNS_FIXED" = true ]; then
|
|
sed -i '/# Docker 镜像源/,/^$/d' /etc/hosts 2>/dev/null || true
|
|
printf "\n# Docker 镜像源 (由 install_docker.sh 写入, 绕过内网DNS)\n%s\n" "$HOSTS_BLOCK" >> /etc/hosts
|
|
success "DNS hosts 修复完毕 ✓"
|
|
else
|
|
warn "公共 DNS 不可达, 跳过 hosts 修复 (pull 时可能遇到解析失败)"
|
|
fi
|
|
|
|
# ---------- 14. 验证安装 ----------
|
|
verify_docker
|
|
|
|
# ---------- 15. 完成提示 ----------
|
|
echo ""
|
|
echo "============================================================"
|
|
success "Docker 安装完成!"
|
|
echo "============================================================"
|
|
echo ""
|
|
echo " 常用命令速查:"
|
|
echo " docker pull <镜像> # 拉取镜像"
|
|
echo " docker push <镜像> # 推送镜像"
|
|
echo " docker commit <容器> <镜像> # 提交容器为镜像"
|
|
echo " docker export <容器> -o x.tar # 导出容器"
|
|
echo " docker load -i <文件.tar> # 加载镜像"
|
|
echo " docker images # 查看本地镜像"
|
|
echo " docker ps -a # 查看所有容器"
|
|
echo ""
|
|
echo " 注意事项:"
|
|
echo " - eth0 网络配置未做任何修改"
|
|
echo " - 本次安装无需重启系统"
|
|
echo " - 镜像加速配置: /etc/docker/daemon.json"
|
|
echo " - Docker 日志: journalctl -u docker 或 /var/log/dockerd.log"
|
|
echo ""
|