2018-05-30php
参考:html
一、《docker从入门到实战》node
二、菜鸟教程http://www.runoob.com/docker/docker-command-manual.html、python
三、docker官网https://docs.docker.com/install/linux/docker-ce/centos/#uninstall-old-versionsmysql
一、卸载旧版本docker
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
二、安装依赖包
$ sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
三、添加国内 yum 软件源
$ sudo yum-config-manager \
--add-repo \
https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repolinux
四、更新 yum 软件源缓存,并安装 docker-ce 。
$ sudo yum makecache fast
$ sudo yum install docker-ce
五、启动 Docker CE:
$ sudo systemctl enable docker
$ sudo systemctl start docker
六、添加用户到docker群组,且注消、从新登陆。docker安装完毕后会自动建立docker群组、自动设置docker群组执行docker相关命令。
$ sudo usermod -aG docker $USERnginx
或者$ sudo gpasswd -a $USER dockergit
从新登陆后验证当前用户已经添加上docker群组:github
$ idgolang
另:退出docker群组的命令(不须要执行):gpasswd -d $USER docker
七、测试 Docker 是否安装正确
$ docker run hello-world
报错:Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.37/images/json: dial unix /var/run/docker.sock: connect: permission denied。
八、配置国内镜像进行加速
新增/etc/docker/daemon.json文件且添加以下内容:
{
"registry-mirrors": [
"https://registry.docker-cn.com"
]
}
重启服务:
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
检查加速器是否生效:
$ docker info
若是从结果中看到了以下内容,说明配置成功:
Registry Mirrors:
https://registry.docker-cn.com/
九、添加内核配置参数
默认配置下,若是在 CentOS 使用Docker CE 看到下面的这些警告信息:
WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled
请添加内核配置参数以启用这些功能:
$ sudo tee -a /etc/sysctl.conf <<-EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
从新加载 sysctl.conf 便可:
$ sudo sysctl -p
十、docker卸载
$ sudo yum remove docker-ce
$ sudo rm -rf /var/lib/docker
一、卸载旧版本docker
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
二、安装 docker-latest
$ yum list | grep docker |grep "1."
$ sudo yum install docker-latest.x86_64
五、启动 Docker CE:
$ sudo systemctl enable docker-latest
$ sudo systemctl start docker-latest
三、注消、从新登陆。docker安装完毕后会自动建立docker群组、自动设置docker群组执行docker相关命令。
另:退出docker群组的命令(不须要执行):gpasswd -d $USER docker
一、docker search :从Docker Hub查找镜像
语法
OPTIONS说明:
二、docker pull : 从镜像仓库中拉取或者更新指定镜像
语法
OPTIONS说明:
从私有仓库抓取镜像:docker pull <path_to_registry>/<image>
三、docker push : 将本地的镜像上传到镜像仓库,要先登录到镜像仓库
语法
OPTIONS说明:
四、docker commit :从容器建立一个新的镜像。
语法
OPTIONS说明:
五、docker images : 列出本地镜像。
语法
OPTIONS说明:
docker images -f dangling=true 显示虚悬镜像
docker images prune 删除虚拟镜像
docker images --format "{{.ID}}: {{.Repository}}" 直接列出镜像结果,而且只包含镜像ID和仓库名
docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}" 打算以表格等距显示,而且有标题行,和默认同样,不过本身定义列
六、docker diff : 检查容器里文件结构的更改。
语法
七、docker rmi : 删除本地一个或多少镜像。
语法
OPTIONS说明:
八、docker save : 将指定镜像保存成 tar 归档文件。
语法
OPTIONS说明:
九、docker load : 从.tar压缩文件中加载镜像
语法
十、docker export :将文件系统做为一个tar归档文件导出到STDOUT。
语法
OPTIONS说明:
十一、docker import : 从归档文件中建立镜像。
语法
OPTIONS说明:
十二、tag : 标记镜像
语法
实例:使用docker tag将ubuntu:latest这个镜像标记为127.0.0.1:5000/ubuntu:latest。
$ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
1三、docker history:查看镜像内的历史记录
所谓定制镜像,那必定是以一个镜像为基础,在其上进行定制。就像咱们以前运行了一个nginx镜像的容器,再进行修改同样,基础镜像是必须指定的。而FROM就是指定基础镜像,所以一个Dockerfile中FROM是必备的指令,而且必须是第一条指令。
在Docker Store上有很是多的高质量的官方镜像,有能够直接拿来使用的服务类的镜像,如nginx、redis、mongo、mysql、httpd、php、tomcat等;也有一些方便开发、构建、运行各类语言应用的镜像,如node、openjdk、python、ruby、golang等。能够在其中寻找一个最符合咱们最终目标的镜像为基础镜像进行定制。
若是没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操做系统镜像,如ubuntu、debian、centos、fedora、alpine等,这些操做系统的软件库为咱们提供了更广阔的扩展空间。
除了选择现有镜像为基础镜像外,Docker还存在一个特殊的镜像,名为scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
FROM scratch
...
若是你以scratch为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将做为镜像第一层开始存在。
不以任何系统为基础,直接将可执行文件复制进镜像的作法并不罕见,好比swarm、coreos/etcd。对于Linux下静态编译的程序来讲,并不须要有操做系统提供运行时支持,所需的一切库都已经在可执行文件里了,所以直接FROM scratch会让镜像体积更加小巧。使用Go语言开发的应用不少会使用这种方式来制做镜像,这也是为何有人认为Go是特别适合容器微服务架构的语言的缘由之一。
RUN 多条的写法是彻底没有意义的,不少运行时不须要的东西都被装进了镜像里,好比编译环境、更新的软件包等等。结果就是产生很是臃肿、很是多层的镜像,不只仅增长了构建部署的时间,也很容易出错。这是不少初学Docker的人常犯的一个错误。
Union FS是有最大层数限制的,好比AUFS,曾经是最大不得超过42层,如今是不得超过127层。
首先,以前全部的命令只有一个目的,就是编译、安装redis可执行文件。所以没有必要创建不少层,这只是一层的事情。所以,这里没有使用不少个RUN对一一对应不一样的命令,而是仅仅使用一个RUN指令,并使用&&将各个所需命令串联起来。将以前的7层,简化为了1层。在撰写Dockerfile的时候,要常常提醒本身,这并非在写Shell脚本,而是在定义每一层该如何构建。
而且,这里为了格式化还进行了换行。Dockerfile支持Shell类的行尾添加\的命令换行方式,以及行首#进行注释的格式。良好的格式,好比换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。
此外,还能够看到这一组命令的最后添加了清理工做的命令,删除了为了编译构建所须要的软件,清理了全部下载、展开的文件,而且还清理了apt缓存文件。这是很重要的一步,咱们以前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。所以镜像构建时,必定要确保每一层只添加真正须要添加的东西,任何无关的东西都应该清理掉,就是每一层构建的最后必定要清理掉无关文件。
格式:
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
和 RUN 指令同样,也有两种格式,一种相似于命令行,一种相似于函数调用。COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。好比:
COPY package.json /usr/src/app/
<源路径> 能够是多个,甚至能够是通配符,其通配符规则要知足 Go 的 filepath.Match 规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径> 能够是容器内的绝对路径,也能够是相对于工做目录的相对路径(工做目录能够用 WORKDIR 指令来指定)。目标路径不须要事先建立,若是目录不存在会在复制文件前先行建立缺失目录。
此外,还须要注意一点,使用 COPY 指令,源文件的各类元数据都会保留。好比读、写、执行权限、文件变动时间等。这个特性对于镜像定制颇有用。特别是构建相关文件都在使用 Git进行管理的时候。
ADD 指令和 COPY 的格式和性质基本一致。可是在 COPY 基础上增长了一些功能。
若是 <源路径> 是一个 URL,这种状况下,Docker 引擎会试图去下载这个连接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,若是这并非想要的权限,那么还须要增长额外的一层 RUN 进行权限调整;另外,若是下载的是个压缩包,须要解压缩,也同样还须要额外的一层 RUN 指令进行解压缩。因此不如直接使用 RUN 指令,而后使用 wget 或者 curl 工具下载,处理权限、解压缩、而后清理无用文件更合理。所以,这个功能其实并不实用,并且不推荐使用。
若是 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的状况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
在某些状况下,这个自动解压缩的功能很是有用,好比官方镜像 ubuntu 中:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...
但在某些状况下,若是咱们真的是但愿复制个压缩文件进去,而不解压缩,这时就不可使用 ADD 命令了。
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽量的使用 COPY,由于 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不必定很清晰。最适合使用 ADD 的场合,就是所说起的须要自动解压缩的场合。
另外须要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
所以在 COPY 和 ADD 指令中选择的时候,能够遵循这样的原则,全部的文件复制均使用COPY指令,仅在须要自动解压缩的场合使用ADD。
也是两种格式:
l shell格式:CMD <命令>
l exec格式:CMD ["可执行文件", "参数1", "参数2"...]
l 参数列表格式:CMD ["参数1", "参数2"...]。在指定了ENTRYPOINT指令后,用CMD指定具体的参数。
以前介绍容器的时候曾经说过,Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,须要指定所运行的程序及参数。CMD指令就是用于指定默认的容器主进程的启动命令的。
在运行时能够指定新的命令来替代镜像设置中的这个默认命令,好比,ubuntu镜像默认的CMD是/bin/bash,若是咱们直接docker run -it ubuntu的话,会直接进入bash。咱们也能够在运行时指定运行别的命令,如docker run -it ubuntu cat /etc/os-release。这就是用cat /etc/os-release命令替换了默认的/bin/bash命令了,输出了系统版本信息。
在指令格式上,通常推荐使用exec格式,这类格式在解析时会被解析为 JSON 数组,所以必定要使用双引号",而不要使用单引号。
若是使用shell格式的话,实际的命令会被包装为sh -c的参数的形式进行执行。好比:
CMD echo $HOME
在实际执行中,会将其变动为:
CMD [ "sh", "-c", "echo $HOME" ]
这就是为何咱们可使用环境变量的缘由,由于这些环境变量会被 shell 进行解析处理。提到CMD就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。
Docker 不是虚拟机,容器中的应用都应该之前台执行,而不是像虚拟机、物理机里面那样,用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。
一些初学者将CMD写为:
CMD service nginx start
而后发现容器执行后就当即退出了。甚至在容器内去使用 systemctl 命令结果却发现根本执行不了。这就是由于没有搞明白前台、后台的概念,没有区分容器和虚拟机的差别,依旧在以传统虚拟机的角度去理解容器。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它须要关心的东西。
而使用 service nginx start 命令,则是但愿 upstart 来之后台守护进程形式启动 nginx 服务。而刚才说了 CMD service nginx start 会被理解为 CMD [ "sh", "-c", "service nginx start"] ,所以主进程其实是 sh 。那么当 service nginx start 命令结束后, sh 也就结束了, sh 做为主进程退出了,天然就会令容器退出。
正确的作法是直接执行 nginx 可执行文件,而且要求之前台形式运行。好比:
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT 的格式和 RUN 指令格式同样,分为 exec 格式和 shell 格式。
ENTRYPOINT 的目的和 CMD 同样,都是在指定容器启动程序及参数。 ENTRYPOINT 在运行时也能够替代,不过比 CMD 要略显繁琐,须要经过 docker run 的参数 --entrypoint 来指定。
当指定了 ENTRYPOINT 后, CMD 的含义就发生了改变,再也不是直接的运行其命令,而是将CMD 的内容做为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
那么有了 CMD 后,为何还要有 ENTRYPOINT 呢?这种 <ENTRYPOINT> "<CMD>" 有什么好处么?让咱们来看几个场景。
假设咱们须要一个得知本身当前公网 IP 的镜像,那么能够先用 CMD 来实现:
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]
假如咱们使用 docker build -t myip . 来构建镜像的话,若是咱们须要查询当前公网 IP,只须要执行:
$ docker run myip
当前 IP:61.148.226.66 来自:北京市 联通
嗯,这么看起来好像能够直接把镜像当作命令使用了,不过命令总有参数,若是咱们但愿加参数呢?好比从上面的 CMD 中能够看到实质的命令是 curl ,那么若是咱们但愿显示 HTTP头信息,就须要加上 -i 参数。那么咱们能够直接加 -i 参数给 docker run myip 么?
$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
咱们能够看到可执行文件找不到的报错, executable file not found 。以前咱们说过,跟在镜像名后面的是 command ,运行时会替换 CMD 的默认值。所以这里的 -i 替换了原来的CMD ,而不是添加在原来的 curl -s http://ip.cn 后面。而 -i 根本不是命令,因此天然找不到。
那么若是咱们但愿加入 -i 这参数,咱们就必须从新完整的输入这个命令:
$ docker run myip curl -s http://ip.cn -i
这显然不是很好的解决方案,而使用 ENTRYPOINT 就能够解决这个问题。如今咱们从新用ENTRYPOINT 来实现这个镜像:
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
此次咱们再来尝试直接使用 docker run myip -i :
$ docker run myip
当前 IP:61.148.226.66 来自:北京市 联通
$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive
当前 IP:61.148.226.66 来自:北京市 联通
能够看到,此次成功了。这是由于当存在 ENTRYPOINT 后, CMD 的内容将会做为参数传给ENTRYPOINT ,而这里 -i 就是新的 CMD ,所以会做为参数传给 curl ,从而达到了咱们预期的效果。
启动容器就是启动主进程,但有些时候,启动主进程前,须要一些准备工做。
好比 mysql 类的数据库,可能须要一些数据库配置、初始化的工做,这些工做要在最终的mysql 服务器运行以前解决。
此外,可能但愿避免使用 root 用户去启动服务,从而提升安全性,而在启动服务前还须要以 root 身份执行一些必要的准备工做,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可使用 root 身份执行,方便调试等。
这些准备工做是和容器 CMD 无关的,不管 CMD 为何,都须要事先进行一个预处理的工做。这种状况下,能够写一个脚本,而后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 <CMD> )做为命令,在脚本最后执行。好比官方镜像 redis 中就是这么作的:
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
能够看到其中为了 redis 服务建立了 redis 用户,并在最后指定了 ENTRYPOINT 为 docker-
entrypoint.sh 脚本。
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi
exec "$@"
该脚本的内容就是根据 CMD 的内容来判断,若是是 redis-server 的话,则切换到redis用户身份启动服务器,不然依旧使用 root 身份执行。好比:
$ docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)
格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
这个指令很简单,就是设置环境变量而已,不管是后面的其它指令,如 RUN ,仍是运行时的应用,均可以直接使用这里定义的环境变量。
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。
定义了环境变量,那么在后续的指令中,就可使用这个环境变量。好比在官方 node 镜像Dockerfile 中,就有相似这样的代码:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.ta
r.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=
1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
在这里先定义了环境变量 NODE_VERSION ,其后的 RUN 这层里,屡次使用 $NODE_VERSION 来进行操做定制。能够看到,未来升级镜像构建版本的时候,只须要更新 7.2.0 便可, Dockerfile 构建维护变得更轻松了。
下列指令能够支持环境变量展开:
ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、 STOPSIGNAL 、 ONBUILD 。
能够从这个指令列表里感受到,环境变量可使用的地方不少,很强大。经过环境变量,咱们可让一份 Dockerfile 制做更多的镜像,只需使用不一样的环境变量便可。
格式: ARG <参数名>[=<默认值>]
构建参数和 ENV 的效果同样,都是设置环境变量。所不一样的是, ARG 所设置的构建环境的环境变量,在未来容器运行时是不会存在这些环境变量的。可是不要所以就使用 ARG 保存密码之类的信息,由于 docker history 仍是能够看到全部值的。
Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值能够在构建命令
docker build 中用 --build-arg <参数名>=<值> 来覆盖。
在 1.13 以前的版本,要求 --build-arg 中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 --build-arg 指定的参数,必须在 Dockerfile 中使用了。若是对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,再也不报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用一样的构建流程构建不一样的Dockerfile 的时候比较有帮助,避免构建命令必须根据每一个 Dockerfile 的内容修改。
格式为:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
以前咱们说过,容器运行时应该尽可能保持容器存储层不发生写操做,对于数据库类须要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节咱们会进一步介绍
Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile 中,咱们能够事先指定某些目录挂载为匿名卷,这样在运行时若是用户不指定挂
载,其应用也能够正常运行,不会向容器存储层写入大量数据。
VOLUME /data
这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。固然,运行时能够覆盖这个挂载设置。好比:
docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了Dockerfile 中定义的匿名卷的挂载配置。
格式为 EXPOSE <端口1> [<端口2>...]。
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会由于这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另外一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
此外,在早期 Docker 版本中还有一个特殊的用处。之前全部容器都运行于默认桥接网络中,所以全部容器互相之间均可以直接访问,这样存在必定的安全性问题。因而有了一个 Docker引擎参数 --icc=false,当指定该参数后,容器间将默认没法互访,除非互相间使用了 --links 参数的容器才能够互通,而且只有镜像中 EXPOSE 所声明的端口才能够被访问。这个 --icc=false 的用法,在引入了 docker network 后已经基本不用了,经过自定义网络能够很轻松的实现容器间的互联与隔离。
要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
格式为 WORKDIR <工做目录路径>。
使用 WORKDIR 指令能够来指定工做目录(或者称为当前目录),之后各层的当前目录就被改成指定的目录,如该目录不存在,WORKDIR 会帮你创建目录。
以前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚原本书写,这种错误的理解还可能会致使出现下面这样的错误:
RUN cd /app
RUN echo "hello" > world.txt
若是将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello。缘由其实很简单,在 Shell 中,连续两行是同一个进程执行环境,所以前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不一样,是两个彻底不一样的容器。这就是对 Dockerfile 构建分层存储的概念不了解所致使的错误。
以前说过每个 RUN 都是启动一个容器、执行命令、而后提交存储层文件变动。第一层 RUN cd /app 的执行仅仅是当前进程的工做目录变动,一个内存上的变化而已,其结果不会形成任何文件变动。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更彻底不要紧,天然不可能继承前一层构建过程当中的内存变化。
所以若是须要改变之后各层的工做目录的位置,那么应该使用 WORKDIR 指令。
格式:USER <用户名>
USER 指令和 WORKDIR 类似,都是改变环境状态并影响之后的层。WORKDIR 是改变工做目录,USER 则是改变以后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。
固然,和 WORKDIR 同样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先创建好的,不然没法切换。
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
若是以 root 执行的脚本,在执行期间但愿改变身份,好比但愿以某个已经创建好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都须要比较麻烦的配置,并且在 TTY 缺失的环境下常常出错。建议使用 gosu。
# 创建 redis 用户,并使用 gosu 换另外一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/
gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]
格式:
HEALTHCHECK [选项] CMD <命令>:设置检查容器健康情况的命令
HEALTHCHECK NONE:若是基础镜像有健康检查指令,使用这行能够屏蔽掉其健康检查指令
HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12引入的新指令。
在没有 HEALTHCHECK 指令前,Docker 引擎只能够经过容器内主进程是否退出来判断容器是否状态异常。不少状况下这没问题,可是若是程序进入死锁状态,或者死循环状态,应用进程并不退出,可是该容器已经没法提供服务了。在 1.12 之前,Docker 不会检测到容器的这种状态,从而不会从新调度,致使可能会有部分容器已经没法提供服务了却还在接受用户请求。
而自 1.12 以后,Docker 提供了 HEALTHCHECK 指令,经过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。
当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,若是连续必定次数失败,则会变为 unhealthy。
HEALTHCHECK 支持下列选项:
l --interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
l --timeout=<时长>:健康检查命令运行超时时间,若是超过这个时间,本次健康检查就被视为失败,默认 30 秒;
l --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3次。
和 CMD, ENTRYPOINT 同样,HEALTHCHECK 只能够出现一次,若是写了多个,只有最后一个生效。
在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 同样,分为 shell 格式,和exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。
假设咱们有个镜像是个最简单的 Web 服务,咱们但愿增长健康检查来判断其 Web 服务是否在正常工做,咱们能够用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 能够这么写:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
这里咱们设置了每 5 秒检查一次(这里为了试验因此间隔很是短,实际应该相对较长),若是健康检查命令超过 3 秒没响应就视为失败,而且使用 curl -fs http://localhost/ || exit1 做为健康检查命令。
使用 docker build 来构建这个镜像:
$ docker build -t myweb:v1 .
构建好了后,咱们启动一个容器:
$ docker run -d --name web -p 80:80 myweb:v1
当运行该镜像后,能够经过 docker ps 看到最初的状态为 (health: starting):
$ docker ps
CONTAINER IDIMAGE COMMANDCREATED S
TATUSPORTS NAMES
03e28eb00bd0myweb:v1"nginx -g 'daemon off" 3 seconds ago U
p 2 seconds (health: starting) 80/tcp, 443/tcp web
在等待几秒钟后,再次 docker ps,就会看到健康状态变化为了 (healthy):
$ docker ps
CONTAINER IDIMAGE COMMANDCREATED S
TATUSPORTS NAMES
03e28eb00bd0myweb:v1"nginx -g 'daemon off" 18 seconds agoU
p 16 seconds (healthy) 80/tcp, 443/tcp web
若是健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)。
为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,能够用 docker inspect 来查看。
$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
"FailingStreak": 0,
"Log": [
{
"End": "2016-11-25T14:35:37.940957051Z",
"ExitCode": 0,
"Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</titl
e>\n<style>\nbody {\nwidth: 35em;\nmargin: 0 auto;\nfont-f
amily: Tahoma, Verdana, Arial, sans-serif;\n}\n</style>\n</head>\n<body>\n<h1>Welc
ome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully inst
alled and\nworking. Further configuration is required.</p>\n\n<p>For online documentat
ion and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCo
mmercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n
<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}
格式:ONBUILD <其它指令>。
ONBUILD 是一个特殊的指令,它后面跟的是其它指令,好比 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile 中的其它指令都是为了定制当前镜像而准备的,惟有 ONBUILD 是为了帮助别人定制本身而准备的。
假设咱们要制做 Node.js 所写的应用的镜像。咱们都知道 Node.js 使用 npm 进行包管理,全部依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,须要先进行npm install 才能够得到全部须要的依赖。而后就能够经过 npm start 来启动应用。所以,通常来讲会这样写 Dockerfile:
FROM node:slim
RUN mkdir /app
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]
把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就能够直接拿来启动容器运行。可是若是咱们还有第二个 Node.js 项目也差很少呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那若是有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让咱们继续看这样的场景维护的问题。
若是第一个 Node.js 项目在开发过程当中,发现这个 Dockerfile 里存在问题,好比敲错字了、或者须要安装额外的包,而后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,可是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,可是并不会由于第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。
那么咱们可不能够作一个基础镜像,而后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,从新构建后就继承了基础镜像的更新?好吧,能够,让咱们看看这样的结果。那么上面的这个 Dockerfile 就会变为:
FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]
这里咱们把项目相关的构建指令拿出来,放到子项目里去。假设这个基础镜像的名字为 my-node 的话,各个项目内的本身的 Dockerfile 就变为:
FROM my-node
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
基础镜像变化后,各个项目都用这个 Dockerfile 从新构建镜像,会继承基础镜像的更新。
那么,问题解决了么?没有。准确说,只解决了一半。若是这个 Dockerfile 里面有些东西须要调整呢?好比 npm install 都须要加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,由于涉及到了当前项目的 ./package.json,难道又要一个个修改么?因此说,这样制做基础镜像,只解决了原来的 Dockerfile 的前4条指令的变化问题,然后面三条指令的变化则彻底没办法处理。
ONBUILD 能够解决这个问题。让咱们用 ONBUILD 从新写一下基础镜像的 Dockerfile:
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]
此次咱们回到原始的 Dockerfile,可是此次将项目相关的指令加上 ONBUILD,这样在构建基础镜像的时候,这三行并不会被执行。而后各个项目的 Dockerfile 就变成了简单地:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,以前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、而且针对本项目执行 npm install,生成应用镜像。
当利用docker run来建立容器时,Docker 在后台运行的标准操做包括:
l 检查本地是否存在指定的镜像,不存在就从公有仓库下载。
l 利用镜像建立并启动一个容器。
l 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层。
l 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去。
l 从地址池配置一个 ip 地址给容器。
l 执行用户指定的应用程序。
l 执行完毕后容器被终止。
一、docker run
语法:
OPTIONS说明:
容器中能够运行一些网络应用,要让外部也能够访问这些应用,能够经过-P或-p参数来指定端口映射。
当使用-P标记时,Docker会随机映射一个49000~49900的端口到内部容器开放的网络端口。
使用docker ps -a能够看到,本地主机的49155被映射到了容器的5000端口。此时访问本机的49155端口便可访问容器内web应用提供的界面。
-p则能够指定要映射的端口,而且,在一个指定端口上只能够绑定一个容器。支持的格式有ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort。
使用hostPort:containerPort格式,本地的5000端口映射到容器的5000端口,能够执行:
$ docker run -d -p 5000:5000 training/webapp python app.py
此时默认会绑定本地全部接口上的全部地址。
可使用ip:hostPort:containerPort格式,指定映射使用一个特定地址,好比 localhost 地址127.0.0.1,能够执行:
$ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
使用ip::containerPort格式,绑定localhost的任意端口到容器的5000端口,本地主机会自动分配一个端口。,能够执行:
$ docker run -d -p 127.0.0.1::5000 training/webapp python app.py
还可使用udp标记来指定udp端口
$ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
-p标记能够屡次使用来绑定多个端口
二、docker start
语法:
选项基本与run同样。
三、docker stop
四、docker restart
选项基本与run同样。
五、docker attach : 链接到正在运行中的容器。
选项:
六、docker ps : 列出容器
语法:
OPTIONS说明:
七、docker inspect : 获取容器/镜像的元数据。
语法:
OPTIONS说明:
实例,获取镜像mysql:5.6的元信息:
$ docker inspect mysql:5.6
实例,获取正在运行的容器mymysql的 IP:
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mymysql
$ docker rm trusting_newton
trusting_newton
若是要删除一个运行中的容器,能够添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。
在使用 -d 参数时,容器启动后会进入后台。某些时候须要进入容器进行操做,此时须要使用docker exec 命令。
docker exec命令只用-i交互式操做参数时,因为没有分配伪终端,界面没有咱们熟悉的Linux命令提示符,但命令执行结果仍然能够返回。当-i交互式操做、-t终端参数一块儿使用时,则能够看到咱们熟悉的 Linux 命令提示符。
$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6
$ docker ps
CONTAINER IDIMAGE COMMAND CREATED STATUS
PORTS NAMES
69d137adef7aubuntu:latest "/bin/bash" 18 seconds agoUp 17
seconds zealous_swirles
$ docker exec -i 69d1 bash
ls
bin
boot
dev
...
$ docker exec -it 69d1 bash
root@69d137adef7a:/#
咱们修改了容器的文件,也就是改动了容器的存储层。咱们能够经过docker diff命令看到具体的改动。
$ docker diff webserver
$ docker port nostalgic_morse 5000
127.0.0.1:49155.
$ docker export 7691a814370e > ubuntu.tar
这样将导出容器快照到本地文件。
语法格式为:
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
咱们能够用下面的命令将容器保存为镜像:
$ docker commit --author "Tao Wang <twang2218@gmail.com>" --message "修改了默认网页" webserver nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
其中--author是指定修改的做者,而--message则是记录本次修改的内容。这点和git版本控制类似,不过这里这些信息能够省略留空。
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
此外,也能够经过指定 URL 或者某个目录来导入,例如
$ docker import http://example.com/exampleimage.tgz example/imagerepo
注:用户既可使用 docker load 来导入镜像存储文件到本地镜像库,也可使用 docker import 来导入一个容器快照到本地镜像库。这二者的区别在于容器快照文件将丢弃全部的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时能够从新指定标签等元数据信息。
镜像构建完成后,能够很容易的在当前宿主机上运行,可是,若是须要在其它服务器上使用这个镜像,咱们就须要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
一个 Docker Registry 中能够包含多个仓库(Repository);每一个仓库能够包含多个标签(Tag);每一个标签对应一个镜像。
一般,一个仓库会包含同一个软件不一样版本的镜像,而标签就经常使用于对应该软件的各个版本。咱们能够经过<仓库名>:<标签>的格式来指定具体是这个软件哪一个版本的镜像。若是不给出标签,将以latest做为默认标签。
以 Ubuntu 镜像 为例,ubuntu是仓库的名字,其内包含有不一样的版本标签,如,14.04,16.04。咱们能够经过ubuntu:14.04,或者ubuntu:16.04来具体指定所需哪一个版本的镜像。若是忽略了标签,好比ubuntu,那将视为ubuntu:latest。
仓库名常常以 两段式路径 形式出现,好比jwilder/nginx-proxy,前者每每意味着 Docker Registry 多用户环境下的用户名,后者则每每是对应的软件名。但这并不是绝对,取决于所使用的具体 Docker Registry 的软件或服务。
Docker Registry公开服务是开放给用户使用、容许用户管理镜像的 Registry 服务。通常这类公开服务容许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。
最常使用的 Registry 公开服务是官方的 Docker Hub,这也是默认的 Registry,并拥有大量的高质量的官方镜像。除此之外,还有 CoreOS 的 Quay.io,CoreOS 相关的镜像存储在这里。Google 的 Google Container Registry,Kubernetes 的镜像使用的就是这个服务。
因为某些缘由,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对Docker Hub的镜像服务(Registry Mirror),这些镜像服务被称为加速器。常见的有 阿里云加速器、DaoCloud 加速器 等。使用加速器会直接从国内的地址下载Docker Hub的镜像,比直接从Docker Hub下载速度会提升不少。在 安装 Docker 一节中有详细的配置方法。
国内也有一些云服务商提供相似于Docker Hub的公开服务。好比 时速云镜像仓库、网易云镜像服务、DaoCloud 镜像市场、阿里云镜像库 等。
目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了数量超过 15,000 的镜像。大部分需求均可以经过在Docker Hub中直接下载镜像来实现。
能够在 https://cloud.docker.com 免费注册一个 Docker 帐号。
能够经过执行docker login命令交互式的输入用户名及密码来完成在命令行界面登陆Docker Hub。
能够经过docker logout退出登陆。
能够经过docker search命令来查找官方仓库中的镜像。例如以centos为关键词进行搜索:
$ docker search centos
能够看到返回了不少包含关键字的镜像,其中包括镜像名字、描述、收藏数(表示该镜像的受关注程度)、是否官方建立、是否自动建立。
官方的镜像说明是官方项目组建立和维护的,automated 资源容许用户验证镜像的来源和内容。
根据是不是官方提供,可将镜像资源分为两类。
一种是相似centos这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司建立、验证、支持、提供。这样的镜像每每使用单个单词做为名字。
还有一种类型,好比tianon/centos镜像,它是由 Docker 的用户建立并维护的,每每带有用户名称前缀。能够经过前缀username/来指定使用某个用户提供的镜像,好比 tianon 用户。
另外,在查找的时候经过--filter=stars=N参数能够指定仅显示收藏数量为N以上的镜像。
利用docker pull命令来将它下载到本地。例以下载官方centos镜像到本地:
$ docker pull centos
用户也能够在登陆后经过docker push命令来将本身的镜像推送到 Docker Hub。如下命令中的username请替换为你的 Docker 帐号用户名。
$ docker tag ubuntu:17.10 username/ubuntu:17.10
$ docker images
$ docker push username/ubuntu:17.10
$ docker search username
自动建立(Automated Builds)功能对于须要常常升级镜像内程序来讲,十分方便。
有时候,用户建立了镜像,安装了某个软件,若是软件发布新版本则须要手动更新镜像。
而自动建立容许用户经过Docker Hub指定跟踪一个目标网站(目前支持 GitHub 或BitBucket)上的项目,一旦项目发生新的提交或者建立新的标签(tag),Docker Hub 会自动构建镜像并推送到Docker Hub中。
要配置自动建立,包括以下的步骤:
l 建立并登陆 Docker Hub,以及目标网站;
l 在目标网站中链接账户到 Docker Hub;
l 在Docker Hub中 配置一个自动建立;
l 选取一个目标网站中的项目(须要含Dockerfile)和分支;
l 指定Dockerfile的位置,并提交建立。
以后,能够在Docker Hub的 自动建立页面 中跟踪每次建立的状态。
除了使用公开服务外,用户还能够在本地搭建私有 Docker Registry。Docker 官方提供了Docker Registry 镜像,能够直接使用作为私有 Registry 服务。
开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持docker命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本 Docker Trusted Registry 中,提供了这些高级功能。
除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能。好比,VMWare Harbor 和 Sonatype Nexus。
本文内容基于docker-registryv2.x 版本。
经过获取官方registry镜像来运行。
$ docker run -d -p 5000:5000 --restart=always -v /opt/data/registry:/var/lib/registry --name registry registry
这将使用官方的registry镜像来启动私有仓库。-v参数,将上传的镜像文件放到指定的本地/opt/data/registry目录,默认状况下,仓库会被建立在容器的/var/lib/registry目录下。
建立好私有仓库以后,就可使用docker tag来标记一个镜像,而后推送它到仓库。例如私有仓库地址为127.0.0.1:5000。
先在本机查看已有的镜像。
$ docker images
REPOSITORY TAG IMAGE IDCREATED VIRTUAL SIZE
ubuntu latest ba5877dc9bec6 weeks ago 192.7 MB
使用docker tag将ubuntu:latest这个镜像标记为127.0.0.1:5000/ubuntu:latest。
格式为docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]。
$ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
$ docker images
使用docker push上传标记的镜像。
$ docker push 127.0.0.1:5000/ubuntu:latest
用curl查看仓库中的镜像。能够看到{"repositories":["ubuntu"]},代表镜像已经被成功上传了。或者进入私有仓库容器/var/lib/registry目录下查看。
$ curl 127.0.0.1:5000/v2/_catalog
{"repositories":["ubuntu"]}
先删除已有镜像,再尝试从私有仓库中下载这个镜像。
$ docker image rm 127.0.0.1:5000/ubuntu:latest
$ docker pull 127.0.0.1:5000/ubuntu:latest
$ docker images
若是你不想使用127.0.0.1:5000做为仓库地址,好比想让本网段的其余主机也能把镜像推送到私有仓库。你就得把例如192.168.199.100:5000这样的内网地址做为私有仓库地址,这时你会发现没法成功推送镜像。
这是由于 Docker 默认不容许非HTTPS方式推送镜像。咱们能够经过 Docker 的配置选项来取消这个限制,或者查看下一节配置可以经过HTTPS访问的私有仓库。
Ubuntu 14.04, Debian 7 Wheezy
对于使用upstart的系统而言,编辑/etc/default/docker文件,在其中的DOCKER_OPTS 中增长以下内容:
DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com --insecure-registries=192.168.199.100:5000"
从新启动服务。
$ sudo service docker restart
Ubuntu 16.04+, Debian 8+, centos 7
对于使用systemd的系统,请在/etc/docker/daemon.json中写入以下内容(若是文件不存在请新建该文件)
{
"registry-mirror": [
"https://registry.docker-cn.com"
],
"insecure-registries": [
"192.168.199.100:5000"
]
}
注意:该文件必须符合json规范,不然 Docker 将不能启动。
其余
对于 Docker for Windows 、 Docker for Mac 在设置中编辑daemon.json增长和上边同样的字符串便可。
上一节咱们搭建了一个具备基础功能的私有仓库,本小节咱们来使用Docker Compose搭建一个拥有权限认证、TLS 的私有仓库。
新建一个文件夹,如下步骤均在该文件夹中进行。
若是你拥有一个域名,国内各大云服务商均提供免费的站点证书。你也可使用openssl自行签发证书。
这里假设咱们将要搭建的私有仓库地址为docker.domain.com,下面咱们介绍使用openssl自行签发docker.domain.com的站点 SSL 证书。
第一步建立CA私钥。
$ openssl genrsa -out "root-ca.key" 4096
第二步利用私钥建立CA根证书请求文件。
$ openssl req \
-new -key "root-ca.key" \
-out "root-ca.csr" -sha256 \
-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=Your Company Name Doc
ker Registry CA'
以上命令中-subj参数里的/C表示国家,如CN;/ST表示省;/L表示城市或者地区;/O表示组织名;/CN通用名称。
第三步配置CA根证书,新建root-ca.cnf。
[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash
第四步签发根证书。
$ openssl x509 -req-days 3650-in "root-ca.csr" \
-signkey "root-ca.key" -sha256 -out "root-ca.crt" \
-extfile "root-ca.cnf" -extensions \
root_ca
第五步生成站点SSL私钥。
$ openssl genrsa -out "docker.domain.com.key" 4096
第六步使用私钥生成证书请求文件。
$ openssl req -new -key "docker.domain.com.key" -out "site.csr" -sha256 \
-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=docker.domain.com'
第七步配置证书,新建site.cnf文件。
[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:docker.domain.com, IP:127.0.0.1
subjectKeyIdentifier=hash
第八步签署站点SSL证书。
$ openssl x509 -req -days 750 -in "site.csr" -sha256 \
-CA "root-ca.crt" -CAkey "root-ca.key"-CAcreateserial \
-out "docker.domain.com.crt" -extfile "site.cnf" -extensions server
这样已经拥有了docker.domain.com的网站 SSL 私钥docker.domain.com.key和 SSL 证书 docker.domain.com.crt。
新建ssl文件夹并将docker.domain.com.keydocker.domain.com.crt这两个文件移入,删除其余文件。
私有仓库默认的配置文件位于/etc/docker/registry/config.yml,咱们先在本地编辑config.yml,以后挂载到容器中。
version: 0.1
log:
accesslog:
disabled: true
level: debug
formatter: text
fields:
service: registry
environment: staging
storage:
delete:
enabled: true
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
auth:
htpasswd:
realm: basic-realm
path: /etc/docker/registry/auth/nginx.htpasswd
http:
addr: :443
host: https://docker.domain.com
headers:
X-Content-Type-Options: [nosniff]
http2:
disabled: false
tls:
certificate: /etc/docker/registry/ssl/docker.domain.com.crt
key: /etc/docker/registry/ssl/docker.domain.com.key
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
$ mkdir auth
$ docker run --rm \
--entrypoint htpasswd \
registry \
-Bbn username password > auth/nginx.htpasswd
将上面的usernamepassword替换为你本身的用户名和密码。
version: '3'
services:
registry:
image: registry
ports:
- "443:443"
volumes:
- ./:/etc/docker/registry
- registry-data:/var/lib/registry
volumes:
registry-data:
编辑/etc/hosts
docker.domain.com 127.0.0.1
$ docker-compose up -d
这样咱们就搭建好了一个具备权限认证、TLS 的私有仓库,接下来咱们测试其功能是否正常。
登陆到私有仓库。
$ docker login docker.domain.com
尝试推送、拉取镜像。
$ docker pull ubuntu:17.10
$ docker tag ubuntu:17.10 docker.domain.com/username/ubuntu:17.10
$ docker push docker.domain.com/username/ubuntu:17.10
$ docker image rm docker.domain.com/username/ubuntu:17.10
$ docker pull docker.domain.com/username/ubuntu:17.10
若是咱们退出登陆,尝试推送镜像。
$ docker logout docker.domain.com
$ docker push docker.domain.com/username/ubuntu:17.10
no basic auth credentials
发现会提示没有登陆,不能将镜像推送到私有仓库中。
若是你本机占用了443端口,你能够配置 Nginx 代理,这里再也不赘述。