#!/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 </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 ""