搭建私有 docker 镜像仓库
文章目录
我为什么要搭建自己的 docker 镜像仓库?
- 互联网上国内各类大学和公司的 docker 镜像仓库基本都挂了;
- 公司项目在内网搭建了私有的镜像仓库,借机深入研究一下;
- 容器技术大有可能,为了更实际的目的,更好的跨平台管理自己的各种服务;
这篇博客记录了从搭建 docker 仓库这个想法开始的,若干折腾的过程和解决的问题。我不想写一篇完美的,循规蹈矩的文章,就是以小标题组织的各类折腾的记录。
客户端和服务端
当我们提到一个应用程序,同时还提到它的仓库,那么大概率是有客户端和服务端的区分。比如典型的git,本地安装的 git cli 或 图形 app 是客户端,git仓库(github,gitlab..)就是服务端,docker 也不例外。
Linux 客户端可以用各类发行版的包管理器安装,Windows 和 Mac 一般推荐直接安装桌面软件,官网都有下载。服务端就是 docker 的镜像仓库,官方有一个镜像仓库 docker.io,不过需要代理访问。如果你没有代理,在我寻遍了国内许多大学和大厂曾经的公开仓库,发现全部已经无法使用之后(无法理解原因??),找到了一个靠谱的方式:docker_image_pusher,使用 Github Action 将国外的 Docker 镜像转存到阿里云私有仓库,然后再使用阿里云的服务拉取镜像到本地使用。
修改镜像存储目录
由于镜像占用的存储空间一般都比较大,需要修改默认(/var/lib/docker)的本地镜像的存储目录。编辑 /etc/docker/daemon.json,添加 data-root 即可:
{
"data-root":"/mnt/ds923plus/docker",
}
这里我将镜像的存储目录设置为了挂载到本机的NAS目录。如何挂载NAS的目录到当前机器?
NAS(192.168.31.100) 中建立 /volume1/raspi4 目录,设置 nfs 访问后,执行下面的 mount 命令将内网中的 NAS 的 /volume1/raspi4 挂载到本机的 /mnt/ds923plus 目录:
sudo mount -t nfs 192.168.31.100:/volume1/raspi4 /mnt/ds923plus
编辑 /etc/fstab 文件,主机重启后自动挂载:
192.168.31.100:/volume1/raspi4 /mnt/ds923plus nfs defaults 0 0
搭建私有仓库
docker 仓库也可以使用 docker 来获取,就像编译器也可以用来编译编译器自己😂。docker 的镜像仓库的镜像是: registry:2。参考 docker_image_pusher 中的步骤,从阿里云服务拉取镜像仓库的镜像,下面是一个例子:
docker pull registry.cn-hangzhou.aliyuncs.com/gkimage/linux_arm64_registry:2
启动容器:
docker run -d --name my-registry \
--network host \
-v /mnt/ds923plus/docker-registry:/var/lib/registry \
--restart=always \
registry.cn-hangzhou.aliyuncs.com/gkimage/linux_arm64_registry:2
编辑 /etc/docker/daemon.json,添加你的仓库地址,如果不做端口映射,仓库的默认端口就是 5000:
{
"insecure-registries": ["192.168.31.99:5000"]
}
重启 docker 服务,并测试 curl 调用:
sudo systemctl restart docker
curl http://192.168.31.99:5000/v2/_catalog
现在我们便拥有了一个地址为 192.168.31.99:5000
的内网镜像仓库。这是内网版本的,没有 https,没有登陆验证。先操作一遍验证可行性,稍后我们再考虑安全性。
给私有仓库添加镜像
我们拥有了私有镜像,自然想推送一些常用的镜像进去(如:ubuntu,nginx等),以便 hack 或直接使用,这些常用的镜像我们无需自己制作,可以从其他公开的镜像仓库 pull,再 push 到私有仓库中去。
如果你没有国外代理,可以使用前文提到的阿里云服务来获取镜像。如果你有代理,可以直接从官方仓库拉取镜像,然后push到私有仓库中。拉取都本地的镜像名的前缀是那个远程仓库的名称,需要使用 docker tag 重命名为即将推送的私有仓库的名称,因为推送镜像会解析镜像名前面的 域名(ip):端口
作为要推送的仓库服务的地址。
比如下面这个从官方仓库拉取镜像 ubuntu:22.04 并重命名为 192.168.31.99:5000/ubuntu:22.04
docker pull docker.io/ubuntu:22.04
docker tag ubuntu:22.04 192.168.31.99:5000/ubuntu:22.04
推送到私有仓库:
docker push 192.168.31.99:5000/ubuntu:22.04
值得注意的是,从官方拉取的镜像 ubuntu:22.04
省略了前面的仓库信息 docker.io/library/
, 实际的完整的镜像名称应该是 docker.io/library/ubuntu:22.04
。只有官方仓库可以省略,其余私有仓库都需要显式指定仓库地址。
多版本镜像管理
从其他仓库中拉取镜像,再 push 到私有仓库存在的问题是,无法创建多平台兼容的镜像版本。
同一个镜像在不同的平台上(linux/amd64/arm64/..)不一样,为了让自己的镜像能够适配不同的平台,需要拉取不同的平台版本的镜像,然后合并推送到私有仓库里面,一开始我是这样想得。后来发现,默认只能拉取当前平台的镜像(即使指定–platform也不行),此路不通。
可以使用下面的命令直接将官方的多版本镜像拉取到私有仓库,无需经过本地中转:
docker buildx imagetools create --tag <私有仓库地址>/nginx:1.25 docker.io/nginx:1.25
powershell 设置代理
本地访问官方的镜像仓库(docker.io)需要设置代理,linux 设置代码的方式很熟悉,windows 的 powershell 如何设置代理呢?
临时设置,仅当前 session 有效:
$env:HTTP_PROXY = "http://127.0.0.1:7890"
$env:HTTPS_PROXY = "http://127.0.0.1:7890"
$env:NO_PROXY = "localhost,127.0.0.1,192.168.*,.docker.internal"
docker info | Select-String "Proxy" # 检查输出中是否包含代理信息
永久设置,写入环境变量中:
[System.Environment]::SetEnvironmentVariable("HTTP_PROXY", "http://127.0.0.1:7890", "User")
[System.Environment]::SetEnvironmentVariable("HTTPS_PROXY", "http://127.0.0.1:7890", "User")
[System.Environment]::SetEnvironmentVariable("NO_PROXY", "localhost,127.0.0.1,192.168.*,.docker.internal", "User")
暴露到公网
如果我们并非所有时候都在内网使用 或 需要与朋友分享自己的仓库,那么将私有仓库服务暴露到公网,以便随时随地可以获取是很实际的需求。
我的方式是路由器层面做端口转发到内网的一台主机,在内网的这台机器上用 ngnix 反向代理到仓库服务的地址。
比如我在路由器的设置中将 22022 端口转发到了内网 192.168.31.98(反向代理机器) 的 20011 端口。
下面是 192.168.31.98 的 nginx 反向代理的配置的例子,监听 20011 端口的请求,并将请求转发到内网的仓库服务地址 http://192.168.31.99:5000 :
# docker
server {
listen 20011 ssl;
server_name zkai.fun;
ssl_certificate <ssl证书>;
ssl_certificate_key <ssl证书的key>;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://192.168.31.99:5000; # 内网服务的IP和端口
client_max_body_size 1G;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
此时,我们便获得了公网可访问的 docker 私有仓库:zkai.fun:22022,此时镜像的拉取和推送操作均需要使用这个地址。
再次使用 curl 测试,获取当前仓库中的镜像列表:
curl https://zkai.fun:22022/v2/_catalog
考虑安全性
如果不设置登陆认证,任何人都可以从公网操作镜像仓库。为了保证安全性,需要设置镜像仓库的加密验证。
使用 htpasswd 生成密码认证信息,并写入到文件,我们使用该存储用户名和加密密码的文件来启动 docker registry 即可完成 docker 的登陆验证设置
sudo htpasswd -Bbn <用户名> <密码> | sudo tee <加密文件> >/dev/null
生成加密文件之后,设置相关环境变量,启动容器:
docker run -d --name registry \
--network host \
--restart=always \
-v /mnt/ds923plus/docker-registry:/var/lib/registry \
-v /mnt/ds923plus/docker-auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=<加密文件>" \
192.168.31.99:5000/registry:2
容器拉起来后,再次使用就需要验证用户名密码了:
docker login zkai.fun:22022
此时,使用 curl 测试也需要加上用户名密码了:
curl https://<用户名>:<密码>@zkai.fun:22022/v2/_catalog
进阶:Harbor
如果我们需要更丰富的仓库管理功能和安全性,可以使用 VMware 开源的企业级容器镜像仓库:https://github.com/goharbor/harbor