2017-07-07 10:25

(四)Docker 快速入门 - 第一个镜像

镜像仓库

使用 Docker 的镜像仓库和使用 Git 类似,我们常用的命令有:

  • $ sudo docker images [IMAGE]  # 查看本地可用镜像

  • $ sudo docker pull ubuntu:16.04  # 拉取指定版本(标签)的镜像

  • $ sudo docker pull fedora:20  # 如不指定版本,则拉取:latest

  • $ sudo docker search puppet  # 搜索DockerHub上的镜像 (Official是否官方)

通常我们创建一个容器时,会搜索本地是否存在,若不存在会从Docker Hub搜索并 pull。比如下面这个镜像:

$ sudo docker run -i -t jamtur01/puppetmaster /bin/bash

root@<id>:# facter 探测主机信息

root@<id>:# puppet --version 验证puppet

构建自己的镜像

对于之前创建的容器,我们可以修改容器,并使用 docker commit 提交修改过的容器成为一个镜像。但我并不推荐这样做(在实际应用中,不要尝试从容器创建镜像)。首先你可能需要注册一个Docker Hub帐号,然后在终端登陆提交容器。

$ sudo docker login / logout
... login
$ sudo docker commit 40s3ksk3kk76 xxx/apache2
$ sudo docker commit -m"A new custom image" -a"Leon" 40s3ksk3kk76 xxx/apache2:webserver
$ sudo docker images xxx/apache2 # 检查新创建的镜像
Dockerfile 构建镜像

我们每一个 Dockerfile 都要放在其相应的文件夹中,且要 cd 到这个目录中构建镜像,所以在之后的小节中,我们都将不在说明 mkdir 和 cd 过程;

# Sample Nginx Web Dockerfile
# Version: 0.0.1
FROM ubuntu:16.04
MAINTAINER Leon "leon@xxx.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
    >/user/share/nginx/html/index.html
EXPOSE 80
# RUN 指令默认使用: /bin/sh -c 执行;
# 如果需要使用exec则使用数组方式:
# RUN [ "apt-get", "install", "-y", "nginx" ]

现在我们把 Dockerfile 放到 leon_web 文件夹下,开始构建第一个镜像:

$ mkdir leon_web && cd leon_web

$ sudo docker build -t="xxx/leon_web" .  (latest)

$ sudo docker build -t="xxx/leon_web:v1" .

构建失败的处理:

如果中途构建失败,又找不到原因,怎么调试?其实对每一步命令,Docker都会构建一层镜像,所以我们每一层都会得到一个镜像ID,我们使用最后一次构建成功的镜像 ID 创建一个容器,然后在容器中执行那条失败的命令调试。

当我们修正错误,继续构建的时候,基于 Docker 的构建缓存机制,将会从第一次失败的地方继续构建。

如果想从头重新构建,则使用:

$ sudo docker build --no-cache -t="xxx/leon_web" .

镜像构建过程

$docker history d1b55fd07600

删除镜像(本地)

$ sudo docker rmi your_user/your_image

Push Docker Hub

$ sudo docker push you_hub_user/your_image

自建仓库

安装 Registry 非常简单(了解更多:https://docs.docker.com/registry),我们安装一个可以自启的 Registry 容器即可:

$ docker run -d -p 5000:5000 --restart=always --name registry registry.docker-cn.com/library/registry:2

$ docker tag ubuntu localhost:5000/my_image  #标记本地ubuntu镜像指向Registry!

$ docker push localhost:5000/my_image  # push 到仓库

$ docker pull localhost:5000/myfirstimage # 从仓库 pull

$ docker stop registry && docker rm -v registry # 关闭仓库并删除所有数据!

自建仓库 Pull

$ sudo docker pull domain:5000/my_image # 加上主机位置即可

$ sudo docker pull ip:5000/my_image

注:由于docker pull默认使用的是 https 协议,但有时候我们环境只有ip可用,为了让客户机用 ip 能 pull 到镜像,我们需要在客户机上执行下面的操作:

$ echo '{ "insecure-registries":["registry-server-ip:5000"] }' > /etc/docker/daemon.json

$ service docker restart # 重启即可。

网上有很多解决ip问题的方案,当我们考虑安全或者客户机比较多的情况下,我觉得还是使用域名或静态路由+https方案比较靠谱便捷,有兴趣的话可以自行搭建。


Dockerfile 指令集

CMD

类似于RUN,但CMD是在容器启动时要运行的命令,如:

CMD ["/bin/ture"]

CMD ["/bin/bash", "-l"]

注:docker run 中的命令会覆盖CMD指令;CMD如果不使用数组形式,则Docker会在命令前加上 /bin/sh -c 执行该条指令;如果想在启动容器时运行多个进程和命令,可以使用类似 Supervisor 这样的工具;

ENTRYPOINT

与CMD非常类似,实际上 docker run 指定的任何参数都会被当做参数再次传递给ENTRYPOINT,如:

ENTRYPINT ["/usr/sbin/nginx"]

CMD ["-h"]

当我们启动时,如果指定了参数 -g "daemon off",

则最终执行的是:/usr/sbin/nginx -g "daemon off",如启动时不指定参数,则会执行:/usr/sbin/nginx -h;

注:如果需要,甚至可以通过 --entrypoint 覆盖 ENTRYPOINT指令;

WORKDIR

设置指令的工作目录,可以理解为执行命令之前的 $ cd dir;

WORKDIR /opt/webapp/db

RUN bundle install

WORKDIR /opt/webapp

ENTRYPOINT [ "rackup" ]

注:可使用 -w 在运行时覆盖工作目录,如:

$ sudo docker run -ti -w /var/log ubuntu pwd /var/log

ENV

环境变量,设置的ENV将会影响后续任何 RUN 指令并在容器中持久化;

ENV RVM_PATH /home/rvm

ENV RVM_PATH=/home/rvm RVM_ARCHFLAGS="-arch i386"

在其他命令中使用环境变量:

ENV TARGET_DIR /opt/app

WORKDIR $TARGET_DIR

运行时环境变量,只在运行时有效,如:

$ sudo docker run -ti -e "WBE_PORT=8080" ubuntu env

USER

USER nginx  / user:group / uid / uid:gid # 指定镜像运行的用户,默认是root.

VOLUME

VOLUME ["/opt/project", "/data"]

向容器添加卷(可以是存在于一个或多个容器内的特定目录),卷存储在宿主的 /var/lib/docker/volumes 中(位置:$docker inspect -f "{{ range .Mounts }}{{.}}{{end}}" ),为持久化和共享数据提供以下主要特性:

  • 在容器间共享和重用;

  • 共享卷不一定要运行相应的容器;

  • 对卷的修改会直接在卷上反映出来;

  • 更新镜像时不会包含对卷的修改;

  • 卷会一直存在,直到没有容器使用它们。

ADD

将构建环境下的文件和目录复制到镜像中,如:

    ADD software.lic /opt/application/software.lic

    ADD latest.tar.gz /var/www/web/ Docker会自动解压到web目录下;

注:复制解压不会覆盖目标文件;新建的文件和目录模式=0755,UID:GID=0:0;通过ADD添加文件或目录,会使Dockerfile中后续指令都不能继续使用之前的构建缓存。

COPY

非常类似 ADD,区别在于 COPY 只关心在构建上下文中复制本地文件,而不会做文件提取解压的工作。

LABEL

LABEL version="1.0"

LABEL location="New York" type="Data center" role="Web Server"

为Docker镜像添加元数据,为避免创建过多的镜像,推荐把元数据都放在一个LABEL中。

STOPSIGNAL

设置停止容器时发送给系统的调用信号(内核系统调用表或SIGNAME格式中的信号)

ARG

定义在 docker build 命令运行时传递给构建运行时的变量。只能指定 Dockerfile 中定义过的参数,如:ARG build、ARG webapp_user=user

$ docker build --build-arg build=1234 -t leon/webapp .

注:不要使用 ARG 传递证书或密钥之类的安全数据。预定义的ARG变量有:HTTP_PROXY、http_proxy、HTTPS~、FTP~、NO~...

ONBUILD

ONBULILD ADD . /app/src

ONBULILD RUN cd /app/src && make

为镜像添加触发器(trigger),当镜像被用做其他镜像的基础镜像时触发。触发器会在构建过程中插入新指令(除FROM、MAINTAINER、ONBUILD之外指令),我们可以认为这些指令紧跟FROM之后;
情景案例:

FROM ubuntu:16.04
MAINTAINER Leon "leon@weippt.com"
RUN apt-get update && apt-get install -y apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ONBUILD ADD . /var/www/
EXPOSE 80
ENTRYPOINT ["/usr/sbin/apache"]
CMD ["-D", "FOREGROUND"]
# sudo docker build -t="test/apache2" .

现在我们可以基于 test/apache2 构建一个新的镜像:

FROM test/apache2
MAINTAINER Leon "leon@weippt.com"
ENV APPLICATION_NAME webapp
ENV ENVIRONMENT development
# sudo docker build -t="test/webapp" .

注:观察构建过程,可以看到在 FROM 之后,Docker会插入一条 ONBUILD 指定的ADD指令。