一、背景
之前在博客里给Docker系列教程起了个头,讲了怎么在几个主流的系统上安装Docker(《C# Docker开发(一) Docker的安装》)。在讲下面的内容之前,我突然觉得我有必要先讲一下如何搭设私有Registry。
大家都知道github,github上所有的项目都是public的,如果需要将项目设置成private的,就需要付费,所以就出了一个gitlab。Docker官方也有一个集中存放用户push的Docker Image的地方叫做Docker Hub。但push到Docker Hub的Image默认也是公开供任意人访问的,如果是自己公司内部产品需要用的Docker Image,push到Docker Hub显然不合适。Docker官方也推出了一个私有的镜像仓库管理软件:Docker Registry。我们可以在自己的服务器上搭建自己私有的Docker Registry,那些不方便上传到Docker Hub的Docker Image就可以上传到这里。
下面的文章里,默认测试机的IP为:192.168.248.129
二、简单的部署
Docker Registry已经被Docker官方push到了Docker Hub中,在终端里敲下面的命令将Registry镜像从Docker Hub上pull下来就好。
1 |
docker pull registry |
这里没有加版本,所以默认下载latest,也就是v2版本。Registry现在最新的版本是v2版本,相较于一开始的v1版本有很多的差别,v1版本是用python写的,而v2版本则是用Go语言写的,另外,v2版本在安全和性能上做出了很多做了非常多的优化。至于两个版本之间的差距具体可以去网上搜索,这里简单提一下就不做深入了。
等镜像pull完毕,就可以启动镜像了:
1 |
docker run -d -p 5000:5000 --name registry --restart=always registry |
上面的命令会使用Registry镜像后台启动一个名为“registry”的容器,同时将本机5000端口映射到容器的5000端口,至于参数“--restart=always”,它表示自动重启容器,当docker服务被重启后,会默认启动容器。
我们现在将一个镜像(centos:latest)push到我们的私有Registry里去:
1 2 3 |
docker pull centos docker tag centos 192.168.248.129:5000/centos docker push 192.168.248.129:5000/centos |
一般来说上面的命令到最后一条命令的时候就会报错:
Get https://192.168.248.129:5000/v1/_ping: http: server gave HTTP response to HTTPS client
因为Docker自从1.3.x起,与Registry连接时都是默认启用HTTPS的。由于我们在这里启动Registry容器时并没有配置HTTPS证书选项,所以Registry默认会改用HTTP,而Docker Client默认只接受HTTPS,所以就报错了。
解决这个问题非常简单,只需要到Docker配置文件里设置一下:
1 |
vim /etc/docker/daemon.json |
这个配置文件是一个Json文件,内部遵守Json格式。
我们需要在里面加一个名为“insecure-registries”的设置项,值为包含我们私有Registry仓库地址数组,修改后的结果大概如下(第一个“registry-mirrors”设置项是Docker Hub的镜像地址,如何设置Docker Hub的镜像地址可以参考之前的文章):
修改好之后重启Docker服务以启用新配置:
1 2 |
systemctl daemon-reload systemctl restart docker.service |
如果之前启动Registry容器时没有加上“--restart=always”参数的话,要再手动启动Registry容器,否则到时候会抛出getsockopt: connection refused的错误。
下面命令中registry是之前启动时赋予容器的名字:
1 |
docker start registry |
现在就可以push到我们的私有仓库里了:
三、进阶一:部署启用HTTPS的Registry
上一章里我们部署了一个简单的私有Registry。由于没有提供HTTPS的证书,所以Registry默认降级使用HTTP。
这一章节中,笔者将教大家如何部署一个启用HTTPS的Registry。
首先是创建证书,现在网上免费的HTTPS证书有很多,比如说Let's Encrypt之类的,当然有条件的话付费什么的也行。不过在这里我就直接使用OpenSSL自签发证书,自签发证书默认是不被系统信任的,所以在部署之后还有一些小调整。
如果没有安装OpenSSL,那就先安装一下:
1 |
sudo yum install -y openssl |
安装完成之后自签发证书:
1 2 3 4 5 |
#创建存放Registry证书的文件夹 sudo mkdir -p /etc/docker/certs.d/registry #创建时效为365天的证书 sudo openssl req -newkey rsa:4096 -nodes -sha256 -keyout /etc/docker/certs.d/registry/private.key -x509 -days 365 -out /etc/docker/certs.d/registry/public.crt |
为了签发证书,会进行必要的信息输入:
Generating a 4096 bit RSA private key
..........................................................................................................................................++
...........................++
writing new private key to '/etc/docker/certs.d/registry/private.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN (国家名称,中国为CN)
State or Province Name (full name) []:Zhejiang (省份名称)
Locality Name (eg, city) [Default City]:Hangzhou (城市名称)
Organization Name (eg, company) [Default Company Ltd]:Chunfeng Tech (组织名称)
Organizational Unit Name (eg, section) []:IT Dev (部门名称)
Common Name (eg, your name or your server's hostname) []:hub.yanning.wang (主机名称,仅允许域名)
Email Address []:xxxx@xxx.com (邮箱地址)
完成上面的步骤之后,OpenSSL工具就会为您产生HTTPS证书文件,位于/etc/docker/cert.d/registry目录下。public.crt是公钥,private.key是私钥。
1 2 3 4 5 6 |
#设置证书文件只读权限,防止被修改: sudo chmod 444 /etc/docker/certs.d/registry/public.crt sudo chmod 444 /etc/docker/certs.d/registry/private.key #如果启用了SELinux,Docker Server会被系统默认拒绝访问证书文件,需要设置放行 sudo chcon -Rt svirt_sandbox_file_t /etc/docker/certs.d/registry |
由于Docker Server不允许使用纯IP的HTTPS证书(会被提示:x509: cannot validate certificate for 192.168.248.129 because it doesn't contain any IP SANs),所以在之前使用OpenSSL自签发HTTPS证书的时候笔者随手填了一个不存在的子域名:hub.yanning.wang
所以现在还需要把对应的解析关系写入hosts里,以便能正常解析域名地址.由于hosts文件默认情况下非root用户不可写,所以要先修改权限,也可直接使用su切换用户:
1 2 3 4 5 6 7 8 |
#将hosts文件权限设置为可写 sudo chmod 777 /etc/hosts #插入解析记录 echo -e IP地址\\t域名 >> /etc/hosts #恢复hosts文件权限设置 sudo chmod 644 /etc/hosts |
IP地址和域名中间的“\\t”会被转义为水平制表符。本文中情景应键入下面的命令:
1 2 3 |
sudo chmod 777 /etc/hosts echo -e 192.168.248.129\\thub.yanning.wang >> /etc/hosts sudo chmod 644 /etc/hosts |
完成之后刷新一下DNS缓存,或者直接重启服务器使hosts的修改生效。
现在可以使用HTTPS证书文件启动Registry容器了:
1 2 3 4 5 |
docker run -d -p 443:5000 --restart=always --name registry \ -v /etc/docker/certs.d/registry:/certs \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/public.crt \ -e REGISTRY_HTTP_TLS_KEY=/certs/private.key \ registry |
上面的命令中,使用-v参数将宿主机的/etc/docker/certs.d/registry目录挂载到了容器内的/certs目录下,同时用-e参数对“REGISTRY_HTTP_TLS_CERTIFICATE”,“REGISTRY_HTTP_TLS_KEY”这两个环境变量进行赋值,赋予证书文件的路径。另外,HTTPS的端口默认使用443,而Registry容器默认只监听5000端口,所以使用-p参数将宿主机的443端口映射到容器的5000端口,省的以后使用docker tag命令的时候还要带上端口号了。
上面的命令敲好之后看一下Registry容器的状态是不是Up状态:
1 |
docker ps |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
583d75664662 registry "/entrypoint.sh /etc/" 3 minutes ago Up 3 minutes 0.0.0.0:443->5000/tcp registry
如果容器的状态(STATUS)不是Up,而是Restarting之类的,可能是参数有问题,具体信息可以使用docker logs命令查看日志输出。
假设您的Registry已经成功启动,我们现在尝试上传一下镜像:
1 2 3 |
docker pull centos docker tag centos hub.yanning.wang/centos docker push hub.yanning.wang/centos |
一般来说如果是使用从专门的HTTPS签发证书机构签发的HTTPS证书,应该可以直接上传,但我们在上面用的是自己自己签发的HTTPS证书,不被系统所信任,所以会直接证书错误:
x509: certificate signed by unknown authority
对于这种自签发证书不被Docker信任的情况,有两种解决方法:
1.将证书放到对应位置(推荐):
对应位置一般来说就是:
/etc/docker/certs.d/[自签发证书时填写的域名]/ca.crt
本文中自签发证书时填写的域名是hub.yanning.wang,所以笔者我需要执行以下命令:
1 2 3 4 5 6 7 8 |
#创建对应目录 sudo mkdir -p /etc/docker/certs.d/hub.yanning.wang #复制证书公钥文件 sudo cp /etc/docker/certs.d/registry/public.crt /etc/docker/certs.d/hub.yanning.wang/ca.crt #设置只读 sudo chmod 444 /etc/docker/certs.d/hub.yanning.wang/ca.crt |
完成后无需重启Docker Server,可以马上进行push操作:
1 |
docker push hub.yanning.wang/centos |
The push refers to a repository [hub.yanning.wang/centos]
cf516324493c: Pushed
latest: digest: sha256:822de5245dc5b659df56dd32795b08ae42db4cc901f3462fc509e91e97132dc0 size: 529
2.将证书导入系统信任证书列表
笔者不是特别建议这种方法,因为导入容易,删除就比较麻烦了。如果您的Docker版本不高,上面的方法不管用,可以试试这个方法。
CentOS下:
1 2 3 4 5 6 7 8 |
#修改可信证书集文件权限为可写 sudo chmod 777 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem #导入证书 cat 您的证书公钥文件 >> /etc/pki/tls/certs/ca-bundle.crt #恢复可信证书集文件权限设置 sudo chmod 444 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem |
导入成功后重启Docker Server,以使得配置生效。
本文情境使用以下命令:
1 2 3 4 |
sudo chmod 777 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem cat /etc/docker/certs.d/registry/public.crt >> /etc/pki/tls/certs/ca-bundle.crt sudo chmod 444 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem sudo systemctl restart docker.service |
然后就可以进行push了。
Ubuntu 16.04下:
Ubuntu的操作比CentOS简单一些,就是把证书公钥文件复制到指定的文件夹中,然后更新CA证书列表,最后重启DockerServer就好:
1 2 3 |
sudo mv 您的证书公钥文件 /usr/local/share/ca-certificates/t sudo update-ca-certificates sudo service docker restart |
本文情境下使用命令(笔者我在移动文件的时候顺便进行了重命名):
1 2 3 |
sudo mv public.crt /usr/local/share/ca-certificates/hub.yanning.wang.crt sudo update-ca-certificates sudo service docker restart |
完成后就可以进行push操作。
四、进阶二:部署启用身份认证的Registry
完成上面Registry的HTTPS部署之后,还有一个新的问题:因为日后我们的这个镜像仓库是会放我们公司自己的商业产品,不能让其他无关人员进行随意进行连接访问。那该怎么办呢?
这个问题自然不用愁,因为Docker官方已经考虑到了这个身份验证的需求,Registry是可以通过设置进行身份验证的,在启用身份验证的Registry中,没有通过身份验证的用户将不能对Registry进行操作。
Registry一共可以使用三种身份验证模式,分别是silly、token和htpasswd。因为htpasswd用的比较广泛,所以本文在这里只讲述如何启用htpasswd身份验证。
htpasswd其实是apache的一个密码生成工具,它可以将用户名和密码存储在文件之中,同时密码是被加密存放的,提高了安全性。一般来说htpasswd工具在安装了apache(httpd)的系统上都会存在,如果您已经安装有apache(httpd),可以直接键入下面的命令生成密码文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#创建存放密码文件的目录 sudo mkdir -p /etc/docker/auth #修改目录权限为可写 sudo chmod 777 /etc/docker/auth #生成密码并储存到文件中 #htpasswd -Bbn 用户名 密码 >> /etc/docker/auth/registry.htpasswd #例如使用 用户名:admin 密码:admin htpasswd -Bbn admin admin >> /etc/docker/auth/registry.htpasswd #设置密码文件只读 sudo chmod 444 /etc/docker/auth/registry.htpasswd #恢复文件夹drwxr-xr-x的权限设置 sudo chmod 755 /etc/docker/auth #如果启用了SELinux,Docker Server会被系统默认拒绝访问密码文件,需要设置放行 sudo chcon -Rt svirt_sandbox_file_t /etc/docker/auth |
如果您的服务器上没有apache(httpd),您也不想在服务器上安装它们的话,也有替代方案,Docker官方在Registry镜像中封装了htpasswd函数,您只需要执行执行下面的命令替换上面的htpasswd命令就好:
1 2 3 4 5 6 |
#生成密码并储存到文件中 #htpasswd -Bbn 用户名 密码 >> /etc/docker/auth/registry.htpasswd 改为 #docker run --entrypoint htpasswd registry -Bbn 用户名 密码>> /etc/docker/auth/registry.htpasswd #例如使用 用户名:admin 密码:admin docker run --entrypoint htpasswd registry -Bbn admin admin >> /etc/docker/auth/registry.htpasswd |
一切就绪后,使用下面的命令启动Registry容器:
1 2 3 4 5 6 7 8 9 |
docker run -d -p 443:5000 --restart=always --name registry \ -v /etc/docker/auth:/auth \ -e REGISTRY_AUTH=htpasswd \ -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.htpasswd \ -v /etc/docker/certs.d/registry:/certs \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/public.crt \ -e REGISTRY_HTTP_TLS_KEY=/certs/private.key \ registry |
和第三章比起来又多了四个参数,不要慌,都不是什么深奥的东西,多的四个参数分别是
1 2 3 4 |
-v /etc/docker/auth:/auth \ -e REGISTRY_AUTH=htpasswd \ -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.htpasswd \ |
分别的意义是:
第一个参数将宿主机的/etc/docker/auth目录挂载到容器内的/auth目录下;
第二个参数设置环境变量告诉Registry容器使用htpasswd验证模式;
第三个参数的意义笔者不是特别清楚,貌似是指定验证领域;
最后一个参数设置环境变量告诉Registry容器htpasswd密码文件的所在路径。
设置好之后,当别人想再访问我们的私有Registry的时候,必须先进行登录,否则会直接报错:
1 |
docker push hub.yanning.wang/centos |
The push refers to a repository [hub.yanning.wang/centos]
no basic auth credentials
在对需要身份验证的Docker Registry进行操作前,需要使用docker login命令进行登陆
1 |
docker login -u 用户名 -p 密码 Registry地址 |
例:
1 |
docker login -u admin -p admin hub.yanning.wang |
当然你也可以在命令中不带用户名密码参数以保障密码安全:
1 |
docker login Registry地址 |
例:
1 |
docker login hub.yanning.wang |
键入命令后,Docker会询问您用户名和密码,键入后回车,当提示“Login Succeeded”之后,便可以进行操作了。
一般来说,使用docker login命令成功登陆后,您的登陆凭据将一直被保留着。如果您现在不需要了,或公用电脑不想让其他人连接此Registry,可以使用docker logout命令登出:
1 |
docker logout Registry地址 |
例:
1 |
docker logout hub.yanning.wang |
主动注销后,在未重新登陆的情况下,将再无访问此Registry的权限。
五、进阶三:将Registry容器中的文件挂载到宿主机
接下来考虑一个很蛋疼的情况,有一天,一个实习生上了服务器,执行了以下的命令:
1 2 |
docker stop registry docker rm registry |
弹指间,Registry容器中所有push上来的镜像全都没了。可不可怕?
刚刚的比喻可能比较极端,那就再举一个例子:您有一天想要之前启动Registry容器的参数,比如修改端口映射,或者修改HTTPS证书路径等等。
一般这样删除容器重新创建,之前所有push上来的镜像都没了,那可怎么办,难道要重新传一次吗?
为了解决这个问题,我们就需要将容器内存放镜像文件的目录挂载到宿主机上,就算容器被销毁了,镜像依旧可以保留,下次启动Registry容器时,重新将文件挂载回去,就不会出现镜像丢失的情况了。
Registry容器默认会将我们push上去的镜像文件保存到容器内的/var/lib/registry目录下,所以我们只要将宿主机的一个目录使用-v参数挂载到容器的/var/lib/registry目录就可以了,在这里笔者将在宿主机中创建/etc/docker/registry目录用于挂载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#创建本地目录 sudo mkdir -p /etc/docker/registry #如果启用了SELinux,Docker Server会被系统默认拒绝访问此目录,需要设置放行 sudo chcon -Rt svirt_sandbox_file_t /etc/docker/registry #启动Registry容器 docker run -d -p 443:5000 --restart=always --name registry \ -v /etc/docker/registry:/var/lib/registry \ -v /etc/docker/auth:/auth \ -e REGISTRY_AUTH=htpasswd \ -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.htpasswd \ -v /etc/docker/certs.d/registry:/certs \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/public.crt \ -e REGISTRY_HTTP_TLS_KEY=/certs/private.key \ registry #先登陆Registry docker login -u admin -p admin hub.yanning.wang #push些镜像上去 docker push hub.yanning.wang/centos docker push hub.yanning.wang/ubuntu #假装手滑删了Registry容器和镜像 docker rmi hub.yanning.wang/centos docker rmi hub.yanning.wang/ubuntu docker stop registry docker rm registry #再次启动Registry容器 docker run -d -p 443:5000 --restart=always --name registry \ -v /etc/docker/registry:/var/lib/registry \ -v /etc/docker/auth:/auth \ -e REGISTRY_AUTH=htpasswd \ -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.htpasswd \ -v /etc/docker/certs.d/registry:/certs \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/public.crt \ -e REGISTRY_HTTP_TLS_KEY=/certs/private.key \ registry #从Registry中下载先前上传的镜像 docker pull hub.yanning.wang/centos docker pull hub.yanning.wang/ubuntu |
可以看到,我们这次中途故意删除了Registry容器,但重新启动后,Registry容器依旧可以提供先前上传的镜像文件。
六、进阶四:部署使用配置文件的Registry
经过上面几个进阶操作,我们的私有Registry变的高大上起来。但美中不足的是,每次启动Registry时都要输入这么长一串的参数,未免太繁琐了。有没有简单的方法呢?
当然有,Registry在容器内部有一份默认的配置文件,位于/etc/docker/registry/config.yml,其内容可以通过docker exec -it registry /bin/sh命令进入容器,或使用docker save registry > registry.tar保存镜像文件后解包镜像文件,查看config.yml内容。
由于在实际使用中,我们将要挂载我们自己的配置文件到容器内覆盖掉原有文件,所以最好将默认设置拷贝出来,在此基础上进行修改,默认设置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
version: 0.1 log: fields: service: registry storage: cache: blobdescriptor: inmemory filesystem: rootdirectory: /var/lib/registry http: addr: :5000 headers: X-Content-Type-Options: [nosniff] health: storagedriver: enabled: true interval: 10s threshold: 3 |
好的,现在让我们将进阶三中启动Registry容器命令中的环境参数写入配置文件中,并保存为/etc/docker/config/config.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
version: 0.1 log: fields: service: registry storage: cache: blobdescriptor: inmemory filesystem: rootdirectory: /var/lib/registry auth: htpasswd: realm: Registry Realm path: /auth/registry.htpasswd http: addr: :5000 tls: certificate: /certs/public.crt key: /certs/private.key headers: X-Content-Type-Options: [nosniff] health: storagedriver: enabled: true interval: 10s threshold: 3 |
上面的Yaml文档中,auth.httpwd和http.tls部分为我们新加的部分。
完成后启动Registry容器。使用配置文件可以省略掉启动时的需要加的环境变量参数-e,但并不能省略挂载参数-v,如果您愿意,可以将几个文件统一放到一个文件夹内,进行挂载,以节省参数。
1 2 3 4 5 6 |
docker run -d -p 443:5000 --restart=always --name registry \ -v /etc/docker/registry:/var/lib/registry \ -v /etc/docker/auth:/auth \ -v /etc/docker/certs.d/registry:/certs \ -v /etc/docker/config/config.yml:/etc/docker/registry/config.yml \ registry |
七、还想要更加高大上?
上一章节,我们提到了配置文件。其实Registry的配置文件自由度非常高,可以进行自由设置,包括允许删除镜像等等,具体的配置手册请参考Docker官方文档《Configuring a registry》,里面有所有可配置的项目和具体说明,虽然只有英文文档,但写的非常简单易懂,强烈推荐。
小柊
2017年9月27日 10:53:24