RDK_Docker_Tools/install_docker/install_docker.sh
2026-03-18 17:23:23 +08:00

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 ""