某镜像站的运维日记 p1

Mon May 13 2024

某镜像站的运维日记

祖宗之法不可变 我tm直接全部重构(bushi

刚接手这个服务器的时候,硬盘是满的,运维文档是没有的,服务端版本是过期的,还全部是手动安装的,没用 docker,甚至不是包管理器装的

经过一番摸索,大概摸清了一部分服务的运行方式,因为要部署一些新的服务,同时有一些旧的服务需要更新,在考量之后,决定用 Docker 重构,将原有的服务移入容器,同时用容器部署新的服务

同时考虑到这些 compose 之后可能也会成为现在这种没有文档的状态,于是决定采用 git 来管理(至少有了个 commit 记录)

注意 这里是直接 git push server-hostname/srv/compose 将本地的仓库推到服务器上的,这个时候服务器上的仓库应该是一个 Bare Repository,但是这样就没有一个 work tree,也就是说没法直接访问到仓库里面的文件,这显然是不符合预期的,所以我没有用 Bare Repository,而是用了个有 work tree 的仓库,但是手动 git checkout -d <mainBranch>,从分支上 detach,这样既可以将仓库推送到远程,又能直接访问 work tree(虽然好像要多一步,但是想想这好像也不是什么大问题,相当于多了一个确认应用更新后的配置文件的机会)

## 将 Minecraft 服务器移入容器

这个其实不是很有必要,但是想着更新一下服务端版本,顺手升级一下 Java Runtime 的版本,于是用了 ghcr.io/graalvm/jdk-community:21.0.2 这个镜像,然后随手写一个 compose 文件,手动修一下文件权限,直接就能跑😋

services:
  java:
    image: linux.xidian.edu.cn/ghcr/graalvm/jdk-community:21.0.2
    read_only: true
    restart: on-failure:3
    working_dir: /srv
    command: /bin/bash /srv/launch.sh
    volumes:
      - /srv/minecraft-server:/srv:rw
    ports:
      - 25565:25565
      - 127.0.0.1:25575:25575

## 部署 Docker Registry 镜像

我同时部署了 Docker Hub 和 ghcr.io 两个 registry 的镜像

registry 用的是 docker 官方的镜像,具体配置在文档里也都有,这里不复制了(

这里用到了一个比较奇怪的trick,正常来说,一个 registry 实例是只能有一个上游的,而 registry 的 api endpoint 在 /v2 下,所以一般镜像站(如南京大学镜像站),都是用域名做分流的。但是,我们有且仅有一个域名,所以我们必须得用路径做分流。这里就涉及到 docker api 的格式了,如果你访问的是 xxx/user/image 这个镜像,对应到 api 路径就是 /v2/xxx/user/image,那就可以写一个 nginx 的分流规则,把 /v2/docker(/.*) 重写成 /docker/v2$1,然后反代到 docker hub 的镜像,同理把 /v2/ghcr(/.*) 重写成 /ghcr/v2$1,这样就既支持在 docker 的 daemon.json 里面配置镜像,也支持 直接使用 docker pull

但是,你觉得到这里就结束了吗?

除了 docker 用户,还有不少的 podman 用户(甚至可能还有用 nerdctl 进行操作 containerd 的用户,但是我懒得测试了) podman 的 cli 在进行 pull 操作的时候,会先向 /v2 发送一个请求,虽然这个响应是一个空的 json,没什么实际数据,但是 podman 就是要请求这一下,如果响应是 4xx 或者 5xx 就直接报错……而我们刚才的分流并没有考虑到这一点,所以还要对 podman 特别加一个 location 做支持,我这里是把 /v2 直接 302 重定向到 /v2/docker

最后 nginx 的配置如下:

location /v2/docker/ {
  include proxy_params;
  proxy_pass http://localhost:5000/v2/;
}

location /docker/ {
  include proxy_params;
  proxy_pass http://localhost:5000/;
}

location /v2/ghcr/ {
  include proxy_params;
  proxy_pass http://localhost:5001/v2/;
}

location /ghcr/ {
  include proxy_params;
  proxy_pass http://localhost:5001/;
}

location /v2 {
  return 302 /v2/docker/;
}