Ubuntu 17.04 x64 安装 Docker CE 初窥 Dockerfile 部署 Nginx

Docker 是个划时代的开源项目,它彻底释放了计算虚拟化的威力,极大提高了应用的运行效率,降低了云计算资源供应的成本!使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松!

无论是应用开发者、运维人员、还是其他信息技术从业人员,都有必要认识和掌握 Docker,节约有限的时间。

系统要求

要安装Docker CE,您需要这些Ubuntu版本的64位版本:

  • Artful 17.10(Docker CE 17.11 Edge及更高版本)
  • ZESTY 17.04
  • Xenial 16.04(LTS)
  • Trusty 14.04(LTS)

Ubuntu x86_64,Linux armhf,s390x(IBM Z)和ppc64le(IBM Power)架构上支持Docker CE

卸载旧版本

老版本的Docker被称为dockerdocker-engine。如果安装了这些,请将其卸载:

$ apt-get remove docker docker-engine docker.io

使用存储库进行安装

首次在新的主机上安装Docker CE之前,需要设置Docker存储库。之后,您可以从存储库安装和更新Docker

  • 设置存储库

1.更新apt软件包索引:

$ apt-get update

2.安装软件包以允许apt通过HTTPS使用存储库:

$ apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

3.添加Docker的官方GPG密钥:

鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。

$ curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

# 官方源
# $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

4.添加 Docker 软件源

鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。

然后,我们需要向 source.list 中添加 Docker 软件源

$ sudo add-apt-repository \
    "deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \
    $(lsb_release -cs) \
    stable"
    

# 官方源
# $ sudo add-apt-repository \
#    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
#    $(lsb_release -cs) \
#    stable"

以上命令会添加稳定版本的 Docker CE APT 镜像源,如果需要最新或者测试版本的 Docker CE 请将 stable 改为 edge 或者 test。从 Docker 17.06 开始,edge test 版本的 APT 镜像源也会包含稳定版本的 Docker

安装Docker CE

1.更新apt软件包索引。

$ apt-get update

2.安装最新版本的Docker CE,或者转到下一步安装特定版本。任何现有的Docker安装都将被替换。

$ apt-get install docker-ce

3.在生产系统上,您应该安装特定版本的Docker CE,而不是始终使用最新版本。此输出被截断。列出可用的版本。

$ apt-cache madison docker-ce

docker-ce | 17.12.0~ce-0~ubuntu | https://download.docker.com/linux/ubuntu zesty/stable amd64 Packages
docker-ce | 17.09.1~ce-0~ubuntu | https://download.docker.com/linux/ubuntu zesty/stable amd64 Packages
docker-ce | 17.09.0~ce-0~ubuntu | https://download.docker.com/linux/ubuntu zesty/stable amd64 Packages
docker-ce | 17.06.2~ce-0~ubuntu | https://download.docker.com/linux/ubuntu zesty/stable amd64 Packages
docker-ce | 17.06.1~ce-0~ubuntu | https://download.docker.com/linux/ubuntu zesty/stable amd64 Packages
docker-ce | 17.06.0~ce-0~ubuntu | https://download.docker.com/linux/ubuntu zesty/stable amd64 Packages

列表的内容取决于启用了哪个存储库。选择一个特定的版本进行安装。第二列是版本字符串。第三列是存储库名称,它指出了软件包来自哪个存储库,并通过扩展其稳定性级别。要安装特定版本,请将版本字符串附加到包名称,并用等号(=)将它们分开:

$ sudo apt-get install docker-ce=<VERSION>

4.通过运行hello-world 映像验证是否正确安装了Docker CE

$ docker run hello-world

这个命令下载一个测试图像并在容器中运行。容器运行时,会打印一条信息消息并退出。

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete 
Digest: sha256:445b2fe9afea8b4aa0b2f27fe49dd6ad130dfe7a8fd0832be5de99625dad47cd
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://cloud.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

以非root用户身份管理Docker

默认情况下,docker 命令会使用 Unix socketDocker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。

要创建docker组并添加您的用户:

1.创建docker组。

$ sudo groupadd docker

2.将您的用户添加到docker组中。

$ sudo usermod -aG docker $USER

3.注销并重新登录,如果在虚拟机上进行测试,则可能需要重新启动虚拟机才能使更改生效。

4.验证您可以不运行docker命令sudo

$ docker run hello-world

卸载Docker CE

1.卸载Docker CE软件包:

$ sudo apt-get purge docker-ce

2.主机上的图像,容器,卷或自定义配置文件不会自动删除。删除所有图像,容器和卷:

$ sudo rm -rf /var/lib/docker

利用 commit 理解镜像构成

这条命令会用nginx 镜像启动一个容器,命名为 myweb,并且映射了 80 端口,这样我们可以用浏览器去访问这个 nginx 服务器。

$ docker run --name myweb -d -p 80:80 nginx

直接访问:http://localhost;如果使用的是 Docker Toolbox,或者是在虚拟机、云服务器上安装的 Docker,则需要将 localhost 换为虚拟机地址或者实际云服务器地址。

Ubuntu 17.04 x64 安装 Docker CE 初窥 Dockerfile 部署 Nginx

现在,假设我们非常不喜欢这个欢迎页面,我们希望改成欢迎 Docker 的文字,我们可以使用 docker exec 命令进入容器,修改其内容。

$ docker exec -it myweb bash
root@5ceb0c8274ca:/# echo '<h1>Welcome to Docker!</h1>' > /usr/share/nginx/html/index.html
root@5ceb0c8274ca:/# exit
exit

直接访问:http://localhost

Ubuntu 17.04 x64 安装 Docker CE 初窥 Dockerfile 部署 Nginx

我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 docker diff 命令看到具体的改动。COMMENT列有备注!

$  docker diff myweb
C /root
A /root/.bash_history
C /run
A /run/nginx.pid
C /usr/share/nginx/html/index.html
C /var/cache/nginx
D /var/cache/nginx/client_temp
D /var/cache/nginx/fastcgi_temp
D /var/cache/nginx/proxy_temp
D /var/cache/nginx/scgi_temp
D /var/cache/nginx/uwsgi_temp
root@souyunku:~/mydocker#

我们可以用下面的命令将容器保存为镜像:

$ docker commit \
    --author "penglei <admin@souyunku.com>" \
    --message "修改了默认网页" \
    myweb \
    nginx:v2
    
sha256:33b2e2aefccbaba54021c85ef7966c7d488abaa0677728e1b057c56a7734a4f0

其中 --author 是指定修改的作者,而 --message 则是记录本次修改的内容。这点和 git 版本控制相似,不过这里这些信息可以省略留空。

我们可以在 docker image ls 中看到这个新定制的镜像:

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
nginx               v2                  33b2e2aefccb        About a minute ago   108MB
nginx               latest              3f8a4339aadd        3 days ago           108MB
hello-world         latest              f2a91732366c        5 weeks ago          1.85kB
$ docker history nginx:v2
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
33b2e2aefccb        2 minutes ago       nginx -g daemon off;                            325B                修改了默认网页
3f8a4339aadd        3 days ago          /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B                  
<missing>           3 days ago          /bin/sh -c #(nop)  STOPSIGNAL [SIGTERM]         0B                  
<missing>           3 days ago          /bin/sh -c #(nop)  EXPOSE 80/tcp                0B                  
<missing>           3 days ago          /bin/sh -c ln -sf /dev/stdout /var/log/nginx…   22B                 
<missing>           3 days ago          /bin/sh -c set -x  && apt-get update  && apt…   53.2MB              
<missing>           3 days ago          /bin/sh -c #(nop)  ENV NJS_VERSION=1.13.8.0.…   0B                  
<missing>           3 days ago          /bin/sh -c #(nop)  ENV NGINX_VERSION=1.13.8-…   0B                  
<missing>           2 weeks ago         /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B                  
<missing>           2 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B                  
<missing>           2 weeks ago         /bin/sh -c #(nop) ADD file:f30a8b5b7cdc9ba33…   55.3MB 

新的镜像定制好后,我们可以来运行这个镜像。

$ docker run --name web2 -d -p 81:80 nginx:v2
ed8c54aeb3c540981b892c0cdfbf9330114ecc935149190e073f49295f2ae147

直接访问:http://localhost:81;如果使用的是 Docker Toolbox,或者是在虚拟机、云服务器上安装的 Docker,则需要将 localhost 换为虚拟机地址或者实际云服务器地址。

Ubuntu 17.04 x64 安装 Docker CE 初窥 Dockerfile 部署 Nginx

使用 Dockerfile 定制镜像

从刚才的 docker commit 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

还以之前定制 nginx 镜像为例,这次我们使用 Dockerfile 来定制。

在一个空白目录中,建立一个文本文件,并命名为 Dockerfile

$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile

其内容为:

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

这个 Dockerfile 很简单,一共就两行。涉及到了两条指令,FROMRUN

FROM 指定基础镜像

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个 DockerfileFROM 是必备的指令,并且必须是第一条指令。

Docker Store 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginxredismongomysqlhttpdphptomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 nodeopenjdkpythonrubygolang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntudebiancentosfedoraalpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

FROM scratch
...

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如 swarmcoreos/etcd。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。

RUN 执行命令

RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

  • shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。

既然 RUN 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢?比如这样:

FROM debian:jessie

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

之前说过,Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。

而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。

Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。

上面的 Dockerfile 正确的写法应该是这样:

FROM debian:jessie

RUN buildDeps='gcc libc6-dev make' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
    && mkdir -p /usr/src/redis \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -rf /var/lib/apt/lists/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对一一对应不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。

并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。

此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。

构建镜像

好了,让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容,那么让我们来构建这个镜像吧。

Dockerfile 文件所在目录执行:


$ docker build -t nginx:v3 .

Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 3f8a4339aadd
Step 2/2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in 5cdac6d29a52
Removing intermediate container 5cdac6d29a52
 ---> 56a1b67b2533
Successfully built 56a1b67b2533
Successfully tagged nginx:v3

从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 Step 2 中,如同我们之前所说的那样,RUN 指令启动了一个容器 5cdac6d29a52,执行了所要求的命令,并最后提交了这一层 56a1b67b2533,随后删除了所用到的这个容器 5cdac6d29a52

这里我们使用了 docker build 命令进行镜像构建。其格式为:

$ docker build [选项] <上下文路径/URL/->

镜像构建上下文(Context)如果注意,会看到 docker build 命令最后有一个 .. 表示当前目录

其它 docker build 的用法

直接用 Git repo 进行构建

或许你已经注意到了,docker build 还支持从 URL 构建,比如可以直接从 Git repo 中构建:

感谢:漠然提供 Git dockerfile repo

$ docker build https://github.com/mritd/dockerfile.git#:alpine-glibc
...

Executing ca-certificates-20171114-r0.post-deinstall
Executing busybox-1.27.2-r6.trigger
OK: 11 MiB in 14 packages
Removing intermediate container baf41e622959
 ---> aded3329be1b
Step 3/3 : ENV LANG=C.UTF-8
 ---> Running in 8c34e0f4d25b
Removing intermediate container 8c34e0f4d25b
 ---> c493a5aa5eb7
Successfully built c493a5aa5eb7

这行命令指定了构建所需的 Git repo,并且指定默认的 master 分支,构建目录为 /alpine-glibc/,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。

用给定的 tar 压缩包构建

$ docker build http://server/context.tar.gz

如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。

从标准输入中读取 Dockerfile 进行构建

$ docker build - < Dockerfile

$ cat Dockerfile | docker build -

如果标准输入传入的是文本文件,则将其视为 Dockerfile,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。

从标准输入中读取上下文压缩包进行构建

$ docker build - < context.tar.gz

如果发现标准输入的文件格式是 gzip、bzip2 以及 xz 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。

参考:Docker — 从入门到实践

https://www.gitbook.com/download/pdf/book/yeasy/docker_practice

参考:Docker 官网 Get Docker CE for Ubuntu

https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-using-the-convenience-script

Contact

上一篇:离线部署 CDH 5.12.1 及使用 CDH 部署 Hadoop 大数据平台集群服务


下一篇:手把手教你 MongoDB 的安装与详细使用(一)