Docker 折腾记:国内镜像源失效之后,我是怎么把环境重新拉起来的

Docker 折腾记:国内镜像源失效之后,我是怎么把环境重新拉起来的

本来只是想把一个项目顺手丢到云服务器上跑起来,结果第一步 `docker pull` 就卡死了。于是又把 Docker 的安装、换源、日志限制和常用命令重新收拾了一遍。

有些问题并不是你不会用 Docker,而是你刚准备开始用,环境就先给你一拳。

写在前面

其实最近一段时间,我并没有频繁折腾 Docker。

本地开发时我基本都挂着网络工具,拉镜像虽然偶尔慢一点,但通常还能忍;真正让我重新把这套东西捡起来,是前几天想把一个项目部署到云服务器上,结果第一步就出了问题:

terminalbash
docker pull xxx

卡住,失败,重试,继续失败。

那一刻就很真实地意识到,之前那种“Docker 挺省心”的印象,很大程度上建立在本地网络还过得去的前提上。一旦到了国内云服务器环境,很多事情立刻就变味了。

这篇文章适合谁

如果你也遇到过下面这些情况,这篇大概能对上胃口:

  • 本地能拉镜像,服务器就是拉不下来
  • Docker 装好了,但一跑项目就开始补配置
  • 日志不设限制,磁盘迟早被打满
  • 记不住常用命令,每次都得翻之前的笔记

所以这篇文章不打算讲太多“Docker 的伟大原理”,而是更偏向一次真实的整理:
我被环境折腾了一遍之后,最后是怎么把 Docker 重新收拾顺手的。

这次到底踩了什么坑

从 2024 年中开始,国内很多 Docker Hub 镜像加速服务陆续失效,一些原本还能用的高校缓存服务也开始不可用。对平时只在本地开发的人来说,这种变化未必很明显;但只要你开始在国内服务器上部署项目,这个问题几乎绕不过去。

之前

本地偶尔用 Docker,挂着网络工具,基本没什么感觉。

后来

想把项目部署到云服务器上,结果镜像拉取失败。

再后来

开始重新翻资料、找镜像源、改 daemon.json、顺手把日志和 IPv6 也一并收拾了。

更烦的还不只是“拉不下来”本身,而是那种很典型的消耗感:你明明不是在解决业务问题,只是想把环境先跑起来,结果一个 docker pull 就能拖走你半天。

先说结论

能用非大陆服务器折腾 Docker,就尽量别在大陆服务器上硬扛。

不是说完全不能用,而是同样的事情,在不同网络环境下,体验差距会大得离谱。

很多时候大家不是不会 Docker,而是被外部环境拖住了。再叠加国内云服务器普遍不算大方的带宽,拉镜像这件事就更容易变成耐心测试。

当然,现实是现实。多数时候机器已经买了,项目也得上,那就只能接受条件有限,然后尽量把配置做得稳一点。

如果非要用一句话概括 Docker,我自己更喜欢这种不太严谨但够直观的说法:

Docker 就像一个能搬来搬去的运行盒子。你不需要到一台新机器上再手动配一遍环境,直接把盒子拿过去,启动,就能跑。

至于镜像、容器、仓库这些概念,真要展开讲当然也能讲很多,但这篇我不准备往教程文的方向写太深。够用就行,先把问题解决,比把概念背熟更重要。

装好还不够,配置得一次到位

因为后面主要是服务器场景,所以这里还是以 Ubuntu 20.04 为例。

如果系统里已经有旧版 Docker,稳妥起见可以先卸一下。包名不一定叫 docker,也可能是 docker.iodocker-engine 之类:

sudo apt-get remove docker docker-engine docker.io containerd runc

卸不掉也不用太慌,很多时候可以直接继续往下装。安装过程我还是沿用官方源这一套:

install-docker.shbash
sudo apt update
sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

装完之后,先看服务有没有正常起来:

sudo systemctl status docker

正常情况下会看到 active (running)。如果你不想以后每次都在前面加 sudo,还可以顺手把当前用户加进 Docker 用户组:

sudo usermod -aG docker $USER

然后重新登录一次,让权限生效。

不过说实话,真正麻烦的其实不是“装上 Docker”,而是“装完以后别留下隐患”。我现在基本会顺手把 daemon.json 一次配好,主要处理三件事:配置镜像源、限制日志大小、顺手打开 IPv6。这里面最值得强调的其实是日志限制,因为这玩意平时不响不动,一旦没管住,最后很容易把磁盘慢慢吃满。

这是踩过的坑

Docker 默认日志如果不做限制,真的会把硬盘慢慢吃满。
这种问题平时不响不动,等你发现的时候,往往已经开始影响服务了。

我当时记录下来还能用的镜像源是这些。注意,这只是 2024-11-03 当时的可用情况,不是永久有效清单:

mirrors.jsonjson
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://dockerhub.icu",
    "https://docker.anyhub.us.kg",
    "https://docker.1panel.live"
  ]
}

我最后实际使用的配置如下:

daemon.jsonjson
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "3"
  },
  "ipv6": true,
  "fixed-cidr-v6": "fd00:dead:beef:c0::/80",
  "experimental": true,
  "ip6tables": true,
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://dockerhub.icu",
    "https://docker.anyhub.us.kg",
    "https://docker.1panel.live"
  ]
}

如果懒得手动编辑,可以直接这样写进去:

write-daemon-json.shbash
cat > /etc/docker/daemon.json <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "3"
  },
  "ipv6": true,
  "fixed-cidr-v6": "fd00:dead:beef:c0::/80",
  "experimental": true,
  "ip6tables": true,
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://dockerhub.icu",
    "https://docker.anyhub.us.kg",
    "https://docker.1panel.live"
  ]
}
EOF

写完之后记得重载并重启:

sudo systemctl daemon-reload && sudo systemctl restart docker

当然,镜像站这件事也别抱太大幻想。刚开始遇到拉取失败时,第一反应通常都是“找个国内镜像站不就行了”,但真折腾下来就会发现,很多镜像站只同步热门镜像,稍微冷门一点的就未必有;就算有,也可能限流、限速,高峰期体验很差;再往深一点说,第三方镜像服务的同步和维护策略,普通用户其实也很难完全验证。

所以我现在的态度很简单:

镜像站可以救急,但别把它当成唯一依赖。
对稳定性要求高的时候,还是得想办法把仓库、网络、部署链路做得更可控。

顺便提一句,阿里云以前那套镜像源我现在基本不考虑了。老早就有“不再更新”的说法,很多不热门镜像的版本确实偏旧,用起来总让人不踏实。

顺手记一份常用命令

前面解决的是“拉不拉得下来”的问题,下面这部分更像我自己的日常备忘录。不是面面俱到,但基本覆盖了平时最常用的操作。

先说镜像。查看本地镜像最简单:

docker images
docker images -q

搜索和拉取镜像的命令我最常用下面这几条:

image-commands.shbash
docker search mysql
docker search mysql --filter=STARS=3000
docker pull mysql
docker pull mysql:5.7

删除镜像也很直接:

docker rmi -f 镜像ID

到了容器这一层,最常见的还是 docker run。虽然参数很多,但平时高频也就那么几个:--name 指定名字,-d 后台运行,-it 交互进入,-p 做端口映射,-v 做数据挂载,-e 传环境变量。记住这些,日常已经够用了。

查看容器:

docker ps
docker ps -a

控制容器的生命周期:

container-lifecycle.shbash
docker start 容器ID
docker restart 容器ID
docker stop 容器ID
docker kill 容器ID
docker rm 容器ID

这里有个很基础但经常忘的点:正在运行的容器不能直接删,先停掉。

查问题时我最常看的三条命令也放一起:

inspect.shbash
docker logs -tf --tail 10 容器ID
docker top 容器ID
docker inspect 容器ID

进入容器一般我都用:

enter-container.shbash
docker exec -it 容器ID /bin/bash
docker attach 容器ID

其中 docker exec -it 明显更顺手,attach 我平时其实用得不多。至于容器和宿主机之间拷文件,也偶尔会用到:

docker cp 容器ID:/xxx/aaa.txt /xxx

另外,数据挂载这件事我建议别偷懒。很多人刚开始图省事,容器跑起来就行,不挂卷;等容器删了、数据没了,才开始后悔,数据库尤其如此。下面这个 MySQL 例子,基本就是我自己平时会直接照着改的版本:

mysql-run.shbash
docker run -d \
  -p 3310:3306 \
  -v /home/mysql/conf:/etc/mysql/conf.d \
  -v /home/mysql/data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  --name mysql01 \
  mysql:5.7
这条命令做了什么
  • 把宿主机 3310 映射到容器 3306
  • 把 MySQL 配置目录挂出来
  • 把数据库数据目录挂出来
  • 设置 root 密码
  • 后台运行,并命名为 mysql01

对于本地开发和轻量部署来说,这种方式已经够用了。至少容器删了还能再建,数据不会跟着一起没。

如果项目稍微复杂一点,比如同时要起 MySQL、Redis、Nginx、前后端服务,那就别继续硬写一长串 docker run 了。我后来基本默认:单容器临时测试用 docker run,多容器项目直接上 docker compose。最常用的命令也就这一条:

docker compose up -d

关键不在命令本身,而在 docker-compose.yml 写得好不好。小项目手写,大一点的项目参考现成部署方案再改,这样通常更省事。

如果以后真要彻底卸载 Docker,也可以顺手记一下:

remove-docker.shbash
docker container stop $(docker container ls -aq)
docker system prune -a --volumes

sudo apt purge docker-ce
sudo apt autoremove

写在最后

这篇其实不是“我来教你 Docker”,而更像是一次被环境教育之后的收拾现场。

我原本只是想在服务器上顺手部署个项目,结果从拉镜像开始就一路磕绊。到最后回头看,真正浪费时间的并不是 Docker 本身,而是那些明明不属于业务、却又不得不处理的环境问题。

很多技术问题最烦的地方,不是难,
而是它不创造价值,却持续消耗你。

所以我现在的做法很简单:只要准备长期用 Docker,就把镜像源、日志限制、卷挂载这些基础项先配好。这样后面真正开始跑项目时,至少不会再被这些低级但高频的小坑反复绊住。

至于国内 Docker 环境什么时候能重新变得省心一点——老实说,我不太乐观。所以与其等环境变好,不如先把自己的工具链收拾利索。

时隔两年,关于博客,我有了新的答案。
路径规划:一个基于 Qt 的 A* 算法演示器

评论区

评论加载中...