容器化技术之Docker

Posted aidata

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了容器化技术之Docker相关的知识,希望对你有一定的参考价值。

 

一、入门

1 简介

1.1 Docker与传统虚拟机

技术图片

 

 

 技术图片

 

 

1.2 Docker架构

技术图片

 

 

 

2 Docker基本概念

2.1 镜像(Image)

对于Linux而言,内核启动后,会挂载root文件系统为其提供用户空间支持。而Docker镜像,就相当于一个root文件系统。比如官方镜像centos:7.6就包含了完整的一套centos7.6最小系统的root文件系统。

Docker镜像是一个特殊的文件系统,除了提供容器去运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

Docker镜像分层存储

因为镜像包含操作系统完整的root文件系统,其体积往往庞大,因此Docker设计时将其设计为分层存储的架构。镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成或者说,由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完成就不会再发生改变了,后一层上的任何改变只发生在自己这一层。在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变得更容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需要的内容,构建新的镜像。

2.2 容器(Container)

镜像和容器的关系就像java中类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器和镜像一样使用分层存储,每个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照Docker最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用Volume数据卷、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生成周期独立于容器,容器消亡,数据卷不会消亡。

2.3 Docker仓库

镜像构建完成后,可以很容易在当前宿主机上运行,如果需要在其他服务器上使用这个镜像,就需要一个集中的存储、分发镜像的服务。Docker Registry就是这样的服务。一个Docker Registry可以包含多个仓库(Repository),每个仓库可以包含多个标签(Tag),每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以latest作为默认标签。

以centos镜像为例,centos是仓库的名字,其内容包含有不同版本标签,如6.9、7.5。我们可以通过centos:6.9,或centos:7.5来具体指定所需哪个版本的镜像。如果忽略了标签,比如centos,那将视为centos:latest。

仓库经常以两段式路径形式出现,比如study/nginx,前者往往意味着Docker Registry多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,还要看具体情况。

Docker Registry公开仓库

常用的Registry是官方的Docker Hub,这也是默认的Registry。除此外,还有CoreOS的Quay.io,CoreOS相关的镜像存储在这里,Google的Goolgle Container Registry,K8s的镜像使用的就是这个服务。

国内的一些云服务商提供了针对Docker Hub的镜像服务,这些镜像服务被称为加速器。常见的有阿里加速器、DaoCloud加速器等。使用加速器会直接从国内的地址下载Docker Hub的镜像,比直接从Docker Hub下载速度会提高很多。

国内也有一些云服务提供商类似于Docker Hub的公开服务,比如阿里云镜像库、网易云镜像服务、DaoCloud镜像市场等。

私有Docker Registry

用户还可以在本地搭建私有Docker Registry,Docker官方提供了Docker Registry镜像,可以直接使用作为私有Registry服务。

开源的Docker Registry镜像只提供了Docker Registry API的服务端实现,足以支持docker命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本Docker Trusted Registry中,提供了这些高级功能。

除了官方Docker Registry外,还有第三方软件实现了Docker Registry API,甚至提供了用户界面以及一些高级功能,如VMWare Harbor和Sonatype Nexus。

 

3 安装

Docker版本格式是YY.MM,如18.09版本,Stable版本每个季度发行,Edge版本每个月发行,同时Docker划分为CE和EE,CE即社区版,免费,支持周期三个月,EE即企业版,强调安全,付费使用。

本次演示的是CenotOS7下安装

官方文档:https://docs.docker.com/install/linux/docker-ce/centos/

 

系统要求

64位CentOS 7,内核不低于3.10

查看内核版本

[root@node01 bigdata]# uname -r
3.10.0-862.14.4.el7.x86_64

卸载旧版本

yum remove docker                   docker-client                   docker-client-latest                   docker-common                   docker-latest                   docker-latest-logrotate                   docker-logrotate                   docker-engine

使用yum进行安装

[root@node01 bigdata]# yum install docker-ce
Loaded plugins: fastestmirror
Determining fastest mirrors
base                                                                                   | 3.6 kB  00:00:00
epel                                                                                   | 5.4 kB  00:00:00
extras                                                                                 | 2.9 kB  00:00:00
updates                                                                                | 2.9 kB  00:00:00
(1/7): base/7/x86_64/group_gz                                                          | 165 kB  00:00:00
(2/7): epel/x86_64/group_gz                                                            |  90 kB  00:00:00
(3/7): base/7/x86_64/primary_db                                                        | 6.0 MB  00:00:00
(4/7): extras/7/x86_64/primary_db                                                      | 159 kB  00:00:00
(5/7): epel/x86_64/updateinfo                                                          | 1.0 MB  00:00:00
(6/7): epel/x86_64/primary_db                                                          | 6.7 MB  00:00:00
(7/7): updates/7/x86_64/primary_db                                                     | 6.7 MB  00:00:00
No package docker-ce available.
Error: Nothing to do

上面出错了,因为如果安装的是CentOS 7 minimal版本,执行安装提示“没有可用软件包”。此时,需要安装必要的软件依赖及更新docker-ce yum源。

[root@node01 bigdata]# sudo yum install -y yum-utils device-mapper-persistent-data lvm2   

添加docker的yum源

[root@node01 bigdata]# sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Loaded plugins: fastestmirror
adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
repo saved to /etc/yum.repos.d/docker-ce.repo

重新安装

[root@node01 bigdata]# yum install docker-ce

官方还提供了使用脚本安装,该安装方法不需要上面多个步骤,只需要下面一个命令即可

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh --mirror Aliyun

设置开机启动并启动Docker CE

[root@node01 bigdata]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[root@node01 bigdata]# systemctl start docker

查看Docker的信息

[root@node01 bigdata]#docker -info

默认情况下,docker命令会使用Unix socket与Docker引擎通讯,只有root用户和docker用户组的用户才可以访问Docker引擎的Unix socket。一般不会使用root用户进行操作,因此,需要将docker用户加入到docker用户组。当然,我们这里使用的是root用户。

[root@node01 bigdata]# sudo groupadd docker
groupadd: group docker already exists
[root@node01 bigdata]# sudo usermod -aG docker $USER

测试Docker是否安装正确

[root@node01 bigdata]# docker run hello-world

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://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

卸载Docker

[root@node01 bigdata]# yum remove docker-ce
[root@node01 bigdata]# rm -rf /var/lib/docker

CentOS 7配置镜像加速

[root@node01 bigdata]# cd /etc/docker/
[root@node01 docker]# ll
total 4
-rw------- 1 root root 244 Feb 22 13:52 key.json
[root@node01 docker]# vi daemon.json

写入

{
    "registry-mirrors":[
        "http://hub-mirror.c.163.com"
    ]
}

重启服务

[root@node01 docker]# systemctl daemon-reload
[root@node01 docker]# systemctl restart docker

 

4 命令

镜像和容器的命令

4.1 Docker镜像操作

Docker运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker会从镜像仓库下载该镜像。

获取镜像

从Docker镜像仓库获取镜像的命令是docker pull,命令格式为:

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

具体的选项可用通过docker pull --help命令看到

Docker镜像仓库地址格式一般是 <域名/IP>[:端口号] ,默认地址是Docker Hub

仓库名通常是两段式格式,即 <用户名>/<软件名> ,对于Docker Hub,如果不给出用户名,则默认为library,也就是官方镜像

docker pull ubuntu:16.04

上面的命令没有给出镜像仓库地址,因此会从Docker Hub获取镜像。镜像名称是Ubuntu:16.04,因此会获取官方镜像library/ubuntu仓库中标签为16.04的镜像。

[root@node01 docker]# docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
fe703b657a32: Pull complete
f9df1fafd224: Pull complete
a645a4b887f9: Pull complete
57db7fe0b522: Pull complete
Digest: sha256:e9938f45e51d9ff46e2b05a62e0546d0f07489b7f22fbc5288defe760599e38a
Status: Downloaded newer image for ubuntu:16.04
docker.io/library/ubuntu:16.04

运行镜像

有了镜像后,可以此镜象为基础启动并运行一个容器。其懂ubuntu:16.04镜像中的bash并且进行交互式操作

[root@node01 docker]# docker run -it --rm ubuntu:16.04 bash
root@1c7aaf91b3a7:/# ll
total 72
drwxr-xr-x  1 root root 4096 Feb 22 07:36 ./
drwxr-xr-x  1 root root 4096 Feb 22 07:36 ../
-rwxr-xr-x  1 root root    0 Feb 22 07:36 .dockerenv*
drwxr-xr-x  2 root root 4096 Feb 12 13:53 bin/
drwxr-xr-x  2 root root 4096 Apr 12  2016 boot/
drwxr-xr-x  5 root root  360 Feb 22 07:36 dev/
drwxr-xr-x  1 root root 4096 Feb 22 07:36 etc/
drwxr-xr-x  2 root root 4096 Apr 12  2016 home/
drwxr-xr-x  8 root root 4096 Sep 13  2015 lib/
drwxr-xr-x  2 root root 4096 Feb 12 13:52 lib64/
drwxr-xr-x  2 root root 4096 Feb 12 13:52 media/
drwxr-xr-x  2 root root 4096 Feb 12 13:52 mnt/
drwxr-xr-x  2 root root 4096 Feb 12 13:52 opt/
dr-xr-xr-x 99 root root    0 Feb 22 07:36 proc/
drwx------  2 root root 4096 Feb 12 13:53 root/
drwxr-xr-x  1 root root 4096 Feb 12 13:52 run/
drwxr-xr-x  1 root root 4096 Feb 21 22:22 sbin/
drwxr-xr-x  2 root root 4096 Feb 12 13:52 srv/
dr-xr-xr-x 13 root root    0 Feb 22 07:36 sys/
drwxrwxrwt  2 root root 4096 Feb 12 13:53 tmp/
drwxr-xr-x  1 root root 4096 Feb 12 13:52 usr/
drwxr-xr-x  1 root root 4096 Feb 12 13:53 var/

-it:这是两个参数,一个是-i,交换操作,一个是-t终端

--rm:这个参数是说容器退出后随之将其删除

bash:放在镜像名后的命令,这里我们希望有个交互式shell,因此用的是bash

通过exit退出容器

列出镜像

列出已经下载下来的镜像

[root@node01 docker]# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              16.04               77be327e4b63        9 hours ago         124MB
hello-world         latest              fce289e99eb9        13 months ago       1.84kB

 docker images 和上面的命令的作用是相同的

查看镜像、容器、数据卷所占用的空间

[root@node01 docker]# docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              2                   1                   124MB               124MB (99%)
Containers          1                   0                   0B                  0B
Local Volumes       0                   0                   0B                  0B
Build Cache         0                   0                   0B                  0B

仓库名、标签均为<none>的镜像称为虚悬镜像(dangling image),显示这类镜像:

[root@node01 docker]# docker image ls -f dangling=true
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

虚悬镜像一般都已经失去了存在的价值,可以用下面的命令删除

[root@node01 docker]# docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

删除本地镜像

如果删除本地的镜像,可以使用docker image rm命令,其格式为:

docker image rm [选项] <镜像1> [<镜像2>...]

使用docker image ls -q来配合docker image rm,这样可以批量删除希望删除的镜像

删除所有名为ubuntu的镜像

docker image rm $(docker image ls -q ubuntu)

删除所有在ubuntu:16.04之前的镜像

docker image rm $(docker image ls -q -f before=ubuntu:16.04)

4.2 Docker容器操作

容器是独立运行的一个或一组应用,以及它们的运行态环境。

启动容器

启动容器有两种方式,一是基于镜像新建一个容器并启动,另外一个是 将终止状态(stopped)的容器重新启动。

因为Docker容器是轻量级的,用户可以随时删除和创建容器。

新建并启动容器

docker run xxx

比如:

[root@node01 docker]# docker run ubuntu:16.04 /bin/echo Hello
Hello

启动已终止的容器

docker container start 或 docker start

示例

[root@h1 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
351f77408a83        ubuntu:16.04        "/bin/bash"         31 minutes ago      Exited (0) 31 minutes ago                       elegant_buck
934e65832863        hello-world         "/hello"            2 days ago          Exited (0) 2 days ago                           eager_brown
[root@h1 ~]# docker start 351f
351f

启动一个bash终端,允许用户进行交互

[root@node01 docker]# docker run -t -i ubuntu:16.04 /bin/bash
root@523ccb47a463:/#

-t让Docker分配一个伪终端并绑定到容器的标准输入上

-i让容器的标准输入保持打开

当利用docker run来创建容器时,Docker在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从共有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个IP地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

后台运行

通过-d参数实现

[root@node01 docker]# docker run hello-world

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://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

[root@node01 docker]# docker run -d hello-world
0346f1ef7cba93bd182aff49254f9682466adc12d0c892272964e6b1769f45f8

使用了-d,不会输出日志,只会打印容器id,输出结果可用docker logs查看

[root@node01 docker]# docker logs 0346f

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://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

停止运行的容器

使用 docker container stop 终止一个运行中的容器

终止状态的容器用 docker container ls -a 命令查看

处于终止状态的容器,通过 docker container start 命令来重新启动

 docker container restart 命令会将一个运行状态的容器终止,然后再重新启动

 

[root@h1 ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              16.04               2a697363a870        4 weeks ago         119MB
hello-world         latest              fce289e99eb9        5 months ago        1.84kB
[root@h1 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
59fdbdff33b1        ubuntu:16.04        "/bin/bash"         2 minutes ago       Exited (0) 2 minutes ago                       wonderful_beaver
8c602ddd28e6        hello-world         "/hello"            4 minutes ago       Exited (0) 4 minutes ago                       clever_volhard
9de6b2eff947        hello-world         "/hello"            4 minutes ago       Exited (0) 4 minutes ago                       blissful_carson
351f77408a83        ubuntu:16.04        "/bin/bash"         40 minutes ago      Exited (0) 6 minutes ago                       elegant_buck
934e65832863        hello-world         "/hello"            2 days ago          Exited (0) 2 days ago                          eager_brown

[root@h1 ~]# docker run -it ubuntu:16.04 /bin/bash
root@cba65e1107cd:/#

打开一个新的会话窗口,查看docker容器情况

[root@h1 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                     PORTS               NAMES
cba65e1107cd        ubuntu:16.04        "/bin/bash"         About a minute ago   Up About a minute                              youthful_volhard
59fdbdff33b1        ubuntu:16.04        "/bin/bash"         4 minutes ago        Exited (0) 4 minutes ago                       wonderful_beaver
8c602ddd28e6        hello-world         "/hello"            6 minutes ago        Exited (0) 6 minutes ago                       clever_volhard
9de6b2eff947        hello-world         "/hello"            6 minutes ago        Exited (0) 6 minutes ago                       blissful_carson
351f77408a83        ubuntu:16.04        "/bin/bash"         43 minutes ago       Exited (0) 8 minutes ago                       elegant_buck
934e65832863        hello-world         "/hello"            2 days ago           Exited (0) 2 days ago                          eager_brown

cba6处于up状态,将其停止

[root@h1 ~]# docker container stop cba6
cba6

旧的会话窗口发现容器已退出

root@cba65e1107cd:/# exit

执行启动和重启命令

[root@h1 ~]# docker container start cba6
cba6
[root@h1 ~]# docker container restart cba6
cba6

进入容器

在使用-d参数时,容器启动后会进入后台,某些时候需要进入容器进行操作,使用 docker exec 命令可用进入到运行的容器中。

-i:只有该参数时,由于没有分配伪终端,界面没有我们熟悉的Linux命令提示符,但命令执行结果仍可返回

-i和-t一起使用时,可用看到命令提示符

[root@h1 ~]# docker exec -it cba6 /bin/bash
root@cba65e1107cd:/#

导出和导入容器

导出

docker export 容器ID>导出文件名.tar

导入

可用使用docker import从容器快照文件中再导入为镜像

cat 导出文件名.tar | docker import - 镜像用户名/镜像名:镜像版本号

也可通过URL或某个目录导入

docker import http://study.163.com/image.tgz example/imagerepo

查看容器

[root@h1 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
cba65e1107cd        ubuntu:16.04        "/bin/bash"         8 minutes ago       Up 3 minutes                                    youthful_volhard
59fdbdff33b1        ubuntu:16.04        "/bin/bash"         11 minutes ago      Exited (0) 11 minutes ago                       wonderful_bea

导出和导入

[root@h1 ~]# docker export cba6 > aaa.zip
[root@h1 ~]# ll
总用量 88000
-rw-r--r--. 1 root root 90104832 6月  18 09:09 aaa.zip
-rw-------. 1 root root     1257 6月   7 00:37 anaconda-ks.cfg
drwxr-xr-x. 2 root root       60 6月  11 10:22 test

[root@h1 ~]# cat aaa.zip | docker import - study/ubuntu:1.0
sha256:a4586c355bc3e8a11d124b0d35362123448a4d1471d2135e44d7b25cbc16f644
[root@h1 ~]# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED                                                                                                SIZE
study/ubuntu        1.0                 a4586c355bc3        29 seconds ago                                                                                         86.1MB
ubuntu              16.04               2a697363a870        4 weeks ago                                                                                            119MB
hello-world         latest              fce289e99eb9        5 months ago 

删除容器

可用使用 docker container rm 来删除一个处于终止状态的容器,运行状态的容器无法删除

[root@h1 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
cba65e1107cd        ubuntu:16.04        "/bin/bash"         13 minutes ago      Up 8 minutes                                    youthful_volhard
59fdbdff33b1        ubuntu:16.04        "/bin/bash"         16 minutes ago      Exited (0) 16 minutes ago                       wonderful_beaver
8c602ddd28e6        hello-world         "/hello"            18 minutes ago      Exited (0) 18 minutes ago                       clever_volhard

[root@h1 ~]# docker container rm 8c60
8c60
[root@h1 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
cba65e1107cd        ubuntu:16.04        "/bin/bash"         14 minutes ago      Up 9 minutes                                    youthful_volhard
59fdbdff33b1        ubuntu:16.04        "/bin/bash"         17 minutes ago      Exited (0) 17 minutes ago                       wonderful_beaver

如果删除一个运行中的容器,可用添加 -f 参数。Docker会发生SIGKILL信号给容器。

清理所有处于终止状态的容器

用docker container ls -a命令可用查看所有可能已经创建的包括终止状态的容器,如果数量太多要一个个删除会很麻烦,使用下面命令:

docker container prune

 

5 构建私有镜像

5.1 使用Dockerfile定制镜像

镜像的定制实际上就是定制每一层所添加的配置、文件。我们可用把每一层修改、安装、构建、操作的命令都写入一个脚本,这个脚本就是Dockerfile。

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

以官方nginx镜像为例,使用Dockerfile来定制。

 

在一个空白目录中,建立一个文本文件,并命名为Dockerfile,Dockerfile第一字母必须大写:

[root@node01 docker]# mkdir mynginx
[root@node01 docker]# cd mynginx/
[root@node01 mynginx]# touch Dockfile

其内容为:

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

FROM指定基础镜像

所谓定制镜像,一定是以一个镜像为基础,在其上进行定制。基础镜像是必须指定的,而FROM就是指定基础镜像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。在Docker Hub上有非常多的高质量的官方镜像,有可用直接拿来使用的服务类的镜像,如nginx、redis、mysql、tomcat等等,可以在其中寻找一个最符合我们最终目标的镜像为基础进行进行制作。

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

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

FROM scratch
...

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

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

RUN执行命令

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

shell格式:RUN <命令>

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

exec格式:RUN["可执行文件", "参数1", "参数2"]

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

5.2 构建镜像

在Dockerfile文件所在目录执行:

[root@node01 mynginx]# docker build -t mynginx:v3 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 2073e0bcb60e
Step 2/2 : RUN echo <h1>Hello,Docker!</h1> > /usr/share/nginx/html/index.html
 ---> Running in 83be5142bcf5
Removing intermediate container 83be5142bcf5
 ---> 56979f0e8e7a
Successfully built 56979f0e8e7a
Successfully tagged nginx:v3
[root@node01 mynginx]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mynginx               v3                  56979f0e8e7a        36 seconds ago      127MB
ubuntu              16.04               77be327e4b63        11 hours ago        124MB

在Step2中,RUN指令启动了一个容器 83be5142bcf5(这是nginx镜像,即基础镜像),执行了所要求的命令,并提交了这一层56979f0e8e7a,随后删除了所用到的这个容器 83be5142bcf5

5.3 Dockerfile指令详解

COPY复制文件

格式:

COPY <源路径>...<目标路径>

COPY ["<源路径1>",..."<目标路径>"]

COPY指令将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内<目标路径>位置。比如:

COPY package.json /usr/src/app/

源路径可以是多个,甚至是通配符,如:

COPY hom*/mydir/
COPY hom?.txt /mydir/

ADD更高级的复制文件

ADD指令和COPY的格式和性质基本一致。但是在COPY基础上增加了一些功能,如源路径是一个URL,则会试图去下载这个链接的文件放到目标路径去。

尽可能使用COPY,仅在需要自动解压缩的场合使用ADD。

继续编辑Docerfile文件

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

创建a.html,里面输入hello, study163.com。创建镜像:

[root@h1 mynginx]# docker build -t mynginx:2.0 .
Sending build context to Docker daemon  3.072kB
Step 1/3 : FROM nginx
 ---> 719cd2e3ed04
Step 2/3 : RUN echo <h1>Hello,Docker!</h1> /usr/share/nginx/html/index.html
 ---> Using cache
 ---> 18dc62484f10
Step 3/3 : COPY a.html /usr/share/nginx/html/
 ---> 503b219881da
Successfully built 503b219881da
Successfully tagged mynginx:2.0


[root@h1 mynginx]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mynginx             2.0                 503b219881da        59 seconds ago      109MB
mynginx             1.0                 18dc62484f10        11 minutes ago      109MB

运行镜像

                                命名     端口映射到宿主机    指定镜像
[root@h1 mynginx]# docker run --name mynginx2 -p 80:80 mynginx:2.0

进入容器

[root@h1 mynginx]# docker exec -it mynginx2 /bin/bash

CMD容器启动命令

CMD指令的格式和RUN相似,也是两种格式:

shell格式:CMD <命令>

exec格式:CMD ["可执行文件", "参数1", "参数2"]

参数列表格式:CMD["参数1", "参数2"...],在指定了ENTRYPOINT指令后,用CMD指定具体的参数。

Docker不是虚拟机,容器是进程,启动时需要指定所运行的程序及参数,CMD指令就是用于指定默认的容器主进程启动命令的。

ENTRYPOINT入口点

目的和CMD一样,都是在指定容器启动程序及参数。在运行时可以替代,不过比CMD更略显繁琐,需要通过docker run 的参数 --entrypoint来指定。

当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给ENTRYPOINT指令,换句话说实际执行时,将其变为:

<ENTRYPOINT>"<CMD>"

ENV设置环境变量

 

格式有两种:

ENV <key><value>
ENV <key1>=<value1> <key2>=<value2>...

这个指令很简单,就是设置环境变量而已,无论是后面的其他指令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。

ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"
$VERSION #使用环境变量

下列指令可以支持环境变量展开:ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD

ARG构建参数

格式:

ARG <参数名>[=<默认值>]

构建参数和EVN的效果一样,都是设置环境变量。但,ARG所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用ARG保存密码之类的信息,因为docker history还是可以看到所有值的。

Dockerfile中ARG指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build中用--build-arg <参数名>=<值>来覆盖。

VOLUME定义匿名卷

格式为:

VOLUME["<路径1>", "<路径2>"...]
VOLUME <路径>

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME/data

这里的/data目录就会在运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。

比如:

docker run -d -v mydata:/data xxxx

在这行命令中,就使用了mydata这个命名卷挂载到了 /data 这个位置,替代了Dockerfile中定义的匿名卷的挂载配置。

EXPOSE声明端口

格式为EXPOSE <端口1> [<端口2>...]

EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就开启这个端口的服务。

Dockerfile中写入这样的声明有两个好处:

  • 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射
  • 在运行时使随机端口映射时,也就是docker run -P时,会自动随机映射EXPOSE端口

WORKDIR指定工作目录

格式为WORDIR <工作目录路径>

使用WORKDIR指令可以来指定工作目录(或称为当前目录),以后各层的当前目录就被改为指定目录,如该目录不存在,WORKDIR会帮你建立。

初学者常见错误把Dockerfile等同于shell脚本来书写,下面错误:

RUN cd/app
RUN echo "hello">world.txt

如果将这个Dockerfile进行构建镜像运行时,会发现找不到/app/world.txt文件

原因

在shell中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令。

而在Dockerfile中,这两个RUN命令的执行环境根本不同,是两个完全不同的容器。这就是对Dockerfile构建分层存储的概念不了解所导致的错误。

每一个RUN都是启动一个容器、执行命令、然后提交存储层文件变更。

第一层RUN cd/app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器完全没关系,自然不可能继承前一层构建过程中的内存变化。

因此如果需要改变以后各层的工作目录的位置,那么应该使用WORKDIR指令。

USER指定当前用户

格式:USER <用户名>

USER指令和WORKDIR相似,都是改变环境状态并影响以后的层。WORKDIR是改变工作目录,USER则是改变之后层的执行RUN,CMD以及ENTRYPOINT这类命令的身份。

当然,和WORKDIR一样,USER只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

RUN groupadd -r redis&&useradd -r -g redis redis
USER redis
RUN ["redis-server"]

HEALTHCHECK健康检查

HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令

HEALTHCHECK NONE:如果基础镜像有健康检查指令,可以屏蔽掉其健康检查指令

HEALTHCHECK指令是告诉Docker应该如何进行判断容器的状态是否正常,这是Docker1.12引入的新命令。通过改指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反映容器实际状态。

一个镜像指定了HEALTHCHECK指令后,用其启动容器,初始状态会为starting,在执行健康检查成功后变为health,如果连续一定次数失败,则会变为unhealthy。

HEALTHCHECK支持下列选项:

  • interval=<间隔>:两次健康检查的间隔,默认为30秒
  • timeout=<时才>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认30秒
  • retries=<次数>:当连续失败指定次数后,则容器状态视为unhealthy,默认3次

为了帮助排出故障,健康检查命令(包括stdout和stderr)都会被存储于健康状态,可以用docker inspect来查看。

ONBUILD

格式:ONBUILD <其他指令>

它后面的是其他指令,比如RUN、COPY等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

Dockerfile中的其他指令都是为了定制当前镜像而准备的,唯有ONBUILD是为了帮助别人定制而准备的。

其他制作镜像的方式

docker save 和docker load

将镜像保存为一个tar文件,然后传输到另一个位置,再加载进来。这是再没有Docker Registry时的做法,现在已经不推荐了。

 

6 通过Dockerfile定制一个私人镜像运行Java程序

 

7 搭建私有仓库

 

 

二、进阶

1 数据挂载

 

2 Compose集成式应用组合及service编排

 

三、最佳实践

1 容器监控

 

2 日志监控

 

3 资源管理

 

4 快速扩容

 

以上是关于容器化技术之Docker的主要内容,如果未能解决你的问题,请参考以下文章

思源笔记Docker容器化部署

容器技术之Docker&K8S

容器技术之Docker&K8S

容器技术之Docker&K8S

如何系统地学习Docker?

Docker的局限之处