Docker 学习入门
一、Docker 简介
1、什么是 Docker
Docker 是使用最广泛的开源容器引擎,它彻底释放了计算虚拟化的威力,极大提高了应用的运行效率,降低了云计算资源供应的成本! 使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松!
Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。
Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。
2、为什么要用 Docker
① 更高效的利用系统资源:由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。
② 更快速的启动时间:Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。
③ 一致的运行环境:Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性。
④ 持续交付和部署:使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。一次创建或配置,可以在任意地方正常运行。
⑤ 更轻松的迁移:Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。
3、Docker 基本组成
① 镜像(Images)
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
Docker 设计时,充分利用 Union FS 的技术,将其设计为分层存储的架构,Docker 镜像由多层文件系统联合组成。镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。
② 容器(Container)
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。
每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层。容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器可以随意删除、重新 run ,数据却不会丢失。
③ 镜像仓库(Registry)
镜像仓库是一个集中的存储、分发镜像的服务。一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
最常使用的 Registry 公开服务是官方的 Docker Hub,这也是默认的 Registry,并拥有大量的高质量的官方镜像。用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。
二、Docker 安装
Docker 版本包含 社区版和企业版,我们日常使用社区版足够。Docker 社区版各个环境的安装参考官方文档:https://docs.docker.com/install/。后面的学习在 Linux CentOS7 上测试。
1、CentOS7 安装步骤
CentOS 安装参考官方文档:https://docs.docker.com/install/linux/docker-ce/centos/
① 卸载旧版本
# yum remove docker docker-common docker-selinux
② 安装依赖包
# yum install -y yum-utils device-mapper-persistent-data lvm2
③ 安装 Docker 软件包源
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
④ 安装 Docker CE
# yum install docker-ce
⑤ 启动 Docker 服务
# systemctl start docker
⑥ 设置开机启动
# systemctl enable docker
⑦ 验证安装是否成功
# docker -v
# docker info
2、Docker 命令
通过 --help 参数可以看到 docker 提供了哪些命令,可以看到 docker 的用法是 docker [选项] 命令 。
命令有两种形式,Management Commands 是子命令形式,每个命令下还有子命令;Commands 是直接命令,相当于子命令的简化形式。
继续查看 Management Commands 有哪些子命令,例如查看 image 的子命令。docker image ls 等同于 docker images,docker image pull 等同于 docker pull。
三、镜像管理
1、镜像简介
镜像包含了一个软件的运行环境,是一个不包含Linux内核而又精简的Linux操作系统,一个镜像可以创建N个容器。
镜像是一个分层存储的架构,由多层文件系统联合组成。镜像构建时,会一层层构建,前一层是后一层的基础。
从下载过程中可以看到,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。
通过 docker history <ID/NAME> 查看镜像中各层内容及大小,每层会对应着 Dockerfile 中的一条指令。
由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
2、镜像管理
Docker 运行容器前需要本地存在对应的镜像,如果镜像不存在本地,Docker 会从镜像仓库下载,默认是 Docker Hub 公共注册服务器中的仓库。
Docker Hub 是由 Docker 公司负责维护的公共注册中心,包含大量的优质容器镜像,Docker 工具默认从这个公共镜像库下载镜像。下载的镜像如何使用可以参考官方文档。地址:https://hub.docker.com/explore
如果从 Docker Hub 下载镜像非常缓慢,可以先配置镜像加速器,参考:https://www.daocloud.io/mirror
Linux下通过以下命令配置镜像站:
# curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io # systemctl restart docker
① 搜索镜像 :docker search <NAME> [选项]
OFFICIAL:是否官方版本;
② 下载镜像 :docker pull [选项] [Docker Registry地址]<仓库名>:<标签>
Docker Registry地址:地址的格式一般是 <域名/IP>[:端口号] 。默认地址是Docker Hub。
仓库名:仓库名是两段式名称,既 <用户名>/<软件名> 。对于 Docker Hub,如果不给出用户名,则默认为 library ,也就是官方镜像。
③ 列出本地镜像 :docker images [选项]
TAG:标签版本;IMAGE ID:镜像ID;SIZE:镜像大小;
查看虚悬镜像(镜像既没有仓库名,也没有标签,显示为 <none>):docker images -f dangling=true
默认的 docker images 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像:docker images -a
只列出镜像ID:docker images -q
列出部分镜像:docker images redis
以特定格式显示:docker images --format "{{.ID}}: {{.Repository}}"
④ 给镜像打 Tag :docker tag <IMAGE ID> [<用户名>/]<镜像名>:<标签>
镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。
⑤ 删除本地镜像 :docker rmi [选项] <镜像1> [<镜像2> ...]
<镜像> 可以是镜像短 ID、镜像长 ID、镜像名或者镜像摘要。
删除镜像的时候,实际上是在要求删除某个标签的镜像。所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的Untagged 的信息。
镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。
除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。
⑥ 批量删除镜像
删除所有虚悬镜像:docker rmi $(docker images -q -f dangling=true)
删除所有仓库名为 redis 的镜像:docker rmi $(docker images -q redis)
删除所有在 mysql:8.0 之前的镜像:docker rmi $(docker images -q -f before=mysql:8.0)
⑦ 导出镜像:docker save -o <镜像文件> <镜像>
可以将一个镜像完整的导出,就可以传输到其它地方去使用。
⑧ 导入镜像:docker load -i <镜像文件>
四、容器管理
1、创建容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。Docker 容器非常轻量级,很多时候可以随时删除和新创建容器。
创建容器的主要命令为 docker run (或 docker container run),常用参数如下:
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 ip 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被终止
① 创建并进入容器:docker run -ti <IMAGE ID OR NAME> /bin/bash
创建容器,通过 -ti 参数分配一个 bash 终端,并进入容器内执行命令。退出容器时容器就被终止了。
② 容器后台运行:docker run -d <IMAGE ID OR NAME>
容器是否会长久运行,是和 docker run 指定的命令有关,即容器内是否有前台进程在运行,和 -d 参数无关。一个容器必须有一个进程来守护容器才会在后台长久运行。
例如通过 -d 参数后台运行 centos,容器创建完成后将退出。而通过 -ti 分配一个伪终端后,容器内将有一个 bash 终端守护容器,容器不会退出。
③ 进入容器:docker exec -ti <CONTAINER ID> bash
④ 指定端口:docker run -d -p <宿主机端口>:<容器内端口> <IMAGE ID>
通过 -p 参数指定宿主机与容器内的端口映射,如果不指定宿主机端口,将会随机映射一个宿主机端口。映射好端口后,宿主机外部就可以通过该端口访问容器了。
⑤ 宿主机重启时自动重启容器:docker run -d --restart always <IMAGE ID>
宿主机重启时,docker 容器默认不会自动启动,可以通过 --restart=always 设置自动重启。
2、容器资源限制
容器资源限制主要是对内存的限制和使用CPU数量的限制,常用选项如下:
① 使用例子
1) 限制容器最多使用500M内存和100M的Swap,并禁用OOM Killer。注意 --memory-swap 的值为 --memory 的值加上 Swap 的值。
docker run -d --name nginx_1 --memory="500m" --memory-swap="600m" --oom-kill-disable nginx
如果 memory 小于等于 memory-swap,表示不使用Swap;如果 memory-swap="-1" 表示可以无限使用Swap;如果不设置 memory-swap,其值默认为 memory 的2倍。
2) 允许容器最多使用一个半的CPU:docker run -d --name nginx_2 --cpus="1.5" nginx
3) 允许容器最多使用 50% 的CPU:docker run -d --name nginx_3 --cpus=".5" nginx
② 查看容器资源使用统计:docker stats <CONTAINER ID>
通过 docker stats 可以实时查看容器资源使用情况。如果不对容器内存或CPU加以限制,将默认使用物理机所有内存和CPU。通常情况下,为了安全一定要限制容器内存和CPU,否则容器内程序遭到攻击,可能无限占用物理机的内存和CPU。
3、容器常用命令
① 停止容器:docker stop <CONTAINER ID>
② 重启容器:docker start <CONTAINER ID>
③ 删除容器
删除已停止的容器:docker rm <CONTAINER ID>
强制删除运行中的容器:docker rm -f <CONTAINER ID>
批量删除Exited(0)状态的容器:docker rm $(docker ps -aq -f exited=0)
批量删除所有容器:docker rm -f $(docker ps -aq)
④ 查看容器日志:docker logs <CONTAINER ID OR NAMES>
⑤ 查看容器详细信息:docker inspect <CONTAINER ID>
⑥ 列出或指定容器端口映射:docker port <CONTAINER ID>
⑦ 显示一个容器运行的进程:docker top <CONTAINER ID>
⑧ 拷贝文件/文件夹到容器:docker cp <dir> <CONTAINER ID>:<dir>
⑨ 查看容器与镜像的差异:docker diff <CONTAINER ID>
五、管理应用程序数据
容器删除后,里面的数据将一同被删除,因此需要将容器内经常变动的数据存储在容器之外,这样删除容器之后数据依然存在。
Docker 提供三种方式将数据从宿主机挂载到容器中:
- volumes:由 Docker 管理的数据卷,是宿主机文件系统的一部分(/var/lib/docker/volumes)。是保存数据的最佳方式。
- bind mounts:将宿主机上的文件或目录挂载到容器中,通常在容器需要使用宿主机上的目录或文件时使用,比如搜集宿主机的信息、挂载宿主机上的 maven 仓库等。
- tmpfs:挂载存储在主机系统的内存中,而不会写入主机的文件系统。如果不希望将数据持久存储在任何位置,可以使用 tmpfs,同时避免写入容器可写层提高性能。这种方式使用比较少。
1、Volume
① 管理卷
创建数据卷:docker volume create <volume_name>
查看数据卷列表:docker volume ls
查看数据卷详细信息:docker volume inspect <volume_name>
创建的数据卷默认在宿主机的 /var/lib/docker/volumes/ 下
② 创建容器时指定数据卷
1) 通过 --mount 方式:--mount src=<数据卷名称>,dst=<容器内的数据目录>,注意逗号之间不能有空格
docker run -d --name nginx_1 --mount src=nginx_html,dst=/usr/share/nginx/html nginx
2) 通过 -v 方式:-v <数据卷名称>:<容器内的数据目录>
docker run -d --name nginx_2 -v nginx_html:/usr/share/nginx/html nginx
以上两种方式,--mount 更加通用直观,-v 是老语法的方式。如果对应的数据卷不存在,将自动创建数据卷。如果挂载目标在容器中非空,则该目录现有内容会自动同步到数据卷中。
③ 删除数据卷:docker volume rm <volume_name>
数据卷是独立于容器的生命周期的,删除容器是不会删除数据卷的,除非删除数据卷,删除数据卷之后数据也就丢失了。
如果数据卷正在被某个容器使用,将不能被删除,需要先删除使用此数据卷的所有容器之后才能删除数据卷。
④ Volume 特点及使用场景
- 多个容器可以同时挂载相同的卷,可用于多个运行容器之间共享数据。
- 当容器停止或被删除后,数据卷依然存在。当明确删除卷时,卷才会被删除。
- 可以将容器的数据存储在远程主机或其它存储上。
- 将数据从一台 docker 主机迁移到另一台主机时,先停止容器,然后备份卷的目录 /var/lib/docker/volumes
2、Bind Mounts
① 创建容器时绑定数据卷
1) 通过 --mount 方式:--mount type=bind,src=<宿主机目录>,dst=<容器内的数据目录>,注意逗号之间不能有空格
docker run -d --name nginx_1 --mount type=bind,src=/data/nginx/html,dst=/usr/share/nginx/html nginx
2) 通过 -v 方式:-v <宿主机目录>:<容器内的数据目录>
docker run -d --name nginx_2 -v /data/nginx/html:/usr/share/nginx/html nginx
以上两种方式,如果源文件/目录不存在,不会自动创建容器,会抛出错误。与 volume 方式相比,如果挂载目标在容器中非空,则该目录现有内容将被隐藏,可以理解成使用宿主机的目录覆盖了容器中的目录,新增的数据会同步。
② Bind Mounts 特点及使用场景
- 从主机共享配置文件到容器。默认情况下,挂载主机 /etc/resolv.conf 到每个容器,提供 DNS 解析。
- 在 docker 主机上的开发环境和容器之间共享源代码。例如,可以将 maven target 目录挂载到容器中,每次在 docker 主机上构建maven项目时,容器都可以访问构建好的项目包。
- 当 docker 主机的文件或目
- 录结构保证与容器所需的绑定挂载一致时,例如容器中需要统计主机的一些信息,可以直接将主机的某些目录直接挂载给容器使用。
六、容器网络
1、Docker 网络模式
① bridge:--net=bridge
默认网络,docker 启动后会创建一个 docker0 网桥,默认创建的容器会添加到这个网桥中。
创建容器时不指定网络模式,默认使用 bridge 方式,通过容器网络配置可以看到会默认分配一个 docker0 的内网IP。
② host:--net=host
容器不会获得一个独立的 network namespace,而是与主机共用一个。这就意味着容器不会有自己的网卡信息,而是使用宿主机的。容器除了网络,其它都是隔离的。
可以看到 host 网络模式下,容器网络配置与宿主机是一样的。那么容器内应用的端口将占用宿主机的端口。
③ none:--net=none
获取独立的 network namespace,但不为容器进行任何网络配置,需要我们手动配置。应用场景比较少。
④ container:--net=container:<CONTAINER NAME/ID>
与指定的容器使用同一个 network namespace,具有同样的网络配置信息,两个容器除了网络,其它都是隔离的。
1) 创建容器,并映射 9090 端口到容器的 80 端口,进入容器内,通过 netstat -antp 可以看到没有端口连接信息
2) 创建 nginx 容器,并使用 net_container 容器的网络。可以看到 net_container 容器内已经在监听 nginx 的80端口了,而且通过 映射的 9090 端口可以访问到 nginx 服务。
⑤ 自定义网络
与默认的bridge原理一样,但自定义网络具备内部DNS发现,可以通过容器名或者主机名进行容器之间网络通信
首先创建一个自定义网络,创建两个容器并加入到自定义网络,在容器中就可以互相连通。
2、容器网络访问原理
当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥(其上有一个 docker0 内部接口),实际上是Linux 的一个 bridge,可以理解为一个软件交换机。它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。
每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口。使用本地主机上 docker0 接口的 IP 作为所有容器的默认网关。
当创建一个 Docker 容器的时候,同时会创建一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0 ;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间的一个虚拟共享网络。
七、制作镜像
1、Dockerfile
镜像是多层存储,每一层是在前一层的基础上进行的修改;Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
我们所使用的镜像基本都是来自于 Docker Hub 的镜像,直接使用这些镜像是可以满足一定的需求,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。
编写 Dockerfile 时,主要会用到如下一些指令:
Dockerfile 中每一个指令都会建立一层,在其上执行后面的命令,执行结束后, commit 这一层的修改,构成新的镜像。对于有些指令,尽量将多个指令合并成一个指令,否则容易产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。而且Union FS 是有最大层数限制的,比如 AUFS不得超过 127 层。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。
① FROM
一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
格式:FROM <镜像名称>[:<TAG>]
Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像并不实际存在,它表示一个空白的镜像。如果以 scratch 为基础镜像的话,意味着不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。
② RUN
RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一。
多个 RUN 指令尽量合并成一个,使用 && 将各个所需命令串联起来。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。
格式:
- shell 格式: RUN <命令> ,就像直接在命令行中输入的命令一样。
- exec 格式: RUN ["可执行文件", "参数1", "参数2"] ,这更像是函数调用中的格式。
③ COPY
COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。<源路径> 可以是多个,甚至可以是通配符。<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径。
使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。
格式:
- COPY <源路径>,... <目标路径>
- COPY ["<源路径1>",... "<目标路径>"]
④ ENV
设置环境变量,无论是后面的其它指令,如 RUN ,还是运行时的应用,都可以直接使用这里定义的环境变量。
格式:
- ENV <key> <value>
- ENV <key1>=<value1> <key2>=<value2>...
⑤ USER
USER 用于切换到指定用户,这个用户必须是事先建立好的,否则无法切换。
格式: USER <用户名>
⑥ EXPOSE
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。
格式:EXPOSE <端口1> [<端口2>...]
⑦ HEALTHCHECK
HEALTHCHECK 指令是告诉 Docker 应该如何判断容器的状态是否正常。通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。
当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting ,在 HEALTHCHECK 指令检查成功后变为 healthy ,如果连续一定次数失败,则会变为 unhealthy
格式:
- HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令,CMD 命令的返回值决定了该次健康检查的成功与否: 0 - 成功; 1 - 失败;。
- HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 支持下列选项:
- --interval=<间隔> :两次健康检查的间隔,默认为 30 秒;
- --timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
- --retries=<次数> :当连续失败指定次数后,则将容器状态视为unhealthy ,默认 3 次。
⑧ WORKDIR
使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,该目录需要已经存在, WORKDIR 并不会帮你建立目录。
在Dockerfile 中,两行 RUN 命令的执行环境是不同的,是两个完全不同的容器。每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。
格式:WORKDIR <工作目录路径>
⑨ VOLUME
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
格式:
- VOLUME ["<路径1>", "<路径2>"...]
- VOLUME <路径>
这里的路径会在运行时自动挂载为匿名卷,任何向此路径中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。
⑩ CMD
CMD 指令用于指定默认的容器主进程的启动命令。
格式:
- shell 格式: CMD <命令>
- exec 格式: CMD ["可执行文件", "参数1", "参数2"...]
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,跟在镜像名后面的是 command ,运行时会替换 CMD 的默认值;例如:docker run -ti nginx /bin/bash,"/bin/bash" 就是替换命令。
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON数组,一定要使用双引号,而不要使用单引号。如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式执行,如:CMD echo $HOME 会被包装为 CMD [ "sh", "-c", "echo $HOME" ],实际就是一个 shell 进程。
注意:Docker 不是虚拟机,容器中的应用都应该以前台执行。对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出。启动程序时,应要求其以前台形式运行。
⑪ ENTRYPOINT
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。 ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。
格式:
- shell 格式: ENTRYPOINT <命令>
- exec 格式: ENTRYPOINT ["可执行文件", "参数1", "参数2"...]
当指定了 ENTRYPOINT 后, CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令。ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
例如: ENTRYPOINT ["/bin/echo", "hello"] 容器通过 docker run -ti <image> 启动时,输出为:hello 容器通过 docker run -ti <image> docker 启动时,输出为:hello docker 将Dockerfile修改为: ENTRYPOINT ["/bin/echo", "hello"] CMD ["world"] 容器通过 docker run -ti <image> 启动时,输出为:hello world 容器通过 docker run -ti <image> docker 启动时,输出为:hello docker
2、构建基础镜像
以构建 nginx 基础镜像为例看如何使用 Dockerfile 构建镜像。
① 编写 Dockerfile
# 基于 centos:8 构建基础镜像 FROM centos:8 # 作者 MAINTAINER jiangzhou.bo@vip.163.com # 安装编译依赖的 gcc 等环境,注意最后清除安装缓存以减小镜像体积 RUN yum install -y gcc gcc-c++ make \ openssl-devel pcre-devel gd-devel \ iproute net-tools telnet wget curl \ && yum clean all \ && rm -rf /var/cache/yum/* # 编译安装 nginx RUN wget http://nginx.org/download/nginx-1.17.4.tar.gz \ && tar zxf nginx-1.17.4.tar.gz \ && cd nginx-1.17.4 \ && ./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module \ && make && make install \ && rm -rf /usr/local/nginx/html/* \ && echo "hello nginx !" >> /usr/local/nginx/html/index.html \ && cd / && rm -rf nginx-1.17.4* # 加入 nginx 环境变量 ENV PATH $PATH:/usr/local/nginx/sbin # 拷贝上下文目录下的配置文件 COPY ./nginx.conf /usr/local/nginx/conf/nginx.conf # 设置工作目录 WORKDIR /usr/local/nginx # 暴露端口 EXPOSE 80 # daemon off: 要求 nginx 以前台进行运行 CMD ["nginx", "-g", "daemon off;"]
② 构建镜像
命令:docker build -t <镜像名称>:<TAG> [-f <Dockerfile 路径>] <上下文目录>
- 上下文目录:镜像构建的上下文。Docker 是 C/S 结构,docker 命令是客户端工具,一切命令都是使用的远程调用形式在服务端(Docker 引擎)完成。当构建的时候,用户指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。这样在 COPY 文件的时候,实际上是复制上下文路径下的文件。一般来说,应该将 Dockerfile 以及所需文件置于一个空目录下,并指定这个空目录为上下文目录。
- Dockerfile 路径:可选,缺省时会寻找当前目录下的 Dockerfile 文件,如果是其它名称的 Dockerfile,可以使用 -f 参数指定文件路径。
在 nginx 目录下包含了构建需要的 Dockerfile 文件及配置文件,docker build 执行时,可以清晰地看到镜像的构建过程。构建成功后,就可以在本地看到已经构建好的镜像。
基于构建的镜像启用容器并测试:
③ 构建应用镜像
构件好基础镜像之后,以这个基础镜像来构建应用镜像。
首先编写 Dockerfile:将项目下的 html 目录拷贝到 nginx 目录下
FROM bojiangzhou/nginx:v1 COPY ./html /usr/local/nginx/html
项目镜像构建成功后,运行镜像,可以看到内容已经被替换了。我们构建好的项目镜像就可以传给其他用户去使用了。
八、镜像仓库
1、Habor
镜像仓库是集中存放镜像的地方。目前 Docker 官方维护了一个公共仓库 Docker Hub,大部分需求,都可以通过在 Docker Hub 中直接下载镜像来实现。
有时候使用 Docker Hub 这样的公共仓库可能不方便,用户可以创建一个本地仓库供私人使用。docker-registry 是官方提供的工具,可以用于构建私有的镜像仓库。
Habor 是由 VMWare 公司开源的容器镜像仓库,Habor 在 docker-registry 上进行了相应的企业级扩展,从而获得了更加广泛的应用。这章学习如何使用 Habor 搭建本地私有仓库。
Harbor 安装及配置要求参考:Installation and Configuration Guide
Harbor 使用文档参考:User Guide
安装 Harbor 前,需确保硬件条件至少 2核4G内存40G的硬盘,本机需已安装 docker、docker-compose、openssl。
2、安装 Docker Compose
Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速在集群中部署分布式应用。使用 Docker Compose 可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具。它允许用户通过一个单独的 docker-compose.yml 模板文件来定义一组相关联的应用容器为一个项目。Harbor 也是基于 docker compose 编排部署的。
① 安装 Docker Compose 可以通过下面命令自动下载适应版本的 Compose,并为安装脚本添加执行权限
# curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose # chmod +x /usr/local/bin/docker-compose
② 检测安装是否成功
# docker-compose -v
3、安装 Harbor
① 首先从 github 下载二进制包,选择一个最新版本安装:https://github.com/goharbor/harbor/releases
# wget https://storage.googleapis.com/harbor-releases/release-1.9.0/harbor-offline-installer-v1.9.1.tgz
② 解压
# tar zxvf harbor-offline-installer-v1.9.1.tgz # cd harbor
③ 修改配置文件
# vim harbor.yml
主要修改 hostname 为可访问的域名或IP、修改管理员密码
④ 安装
# ./prepare # ./install.sh
Harbor 安装成功后,实际上就是通过 docker compose 启动了相关组件的容器
⑤ Harbor 生命周期管理
harbor 基于 docker compose 编排部署,因此可以使用 docker-compose 命令来管理 harbor 的生命周期。
首先切到 harbor 根目录下
停止 harbor:docker-compose stop
重启 harbor:docker-compose start
修改配置:
# #先停止 harbor # docker-compose down -v # #修改 harbor.yml # vim harbor.yml # #运行 prepare 填充配置 # ./prepare # #启动harbor # docker-compose up -d
4、Harbor 简单使用
① 访问 Harbor
访问配置的 hostname 进入 harbor 管理页面。具体如何使用可参考官方文档:User Guide
通过新建项目创建一个项目(bojiangzhou)用于测试。公开项目不需登录就可以下载镜像,私有的需要登录;上传镜像则都需要登录才能上传镜像。
推送镜像的命令格式可以参考如下:
② 创建用户
若想上传镜像,需要用户登录,首先在用户管理创建用户:
接着在项目中添加用户成员:
③ 前置配置
上传镜像之前,首先需要将 harbor 仓库配置为 docker 授信的仓库,否则将被拒绝连接:
vim /etc/docker/daemon.json # 配置如下内容 { "registry-mirrors": ["http://f1361db2.m.daocloud.io"], "insecure-registries": ["192.168.31.54"] }
之后重启 docker 和 harbor:
#systemctl restart docker # cd /docker-test/harbor # docker-compose up -d
④ 镜像仓库登录退出
登录:docker login registry
退出:docker logout registry
⑤ 上传镜像
首先需要给镜像打 Tag:docker tag SOURCE_IMAGE[:TAG] 192.168.31.54/bojiangzhou/IMAGE[:TAG]
接着推送镜像:docker push 192.168.31.54/bojiangzhou/IMAGE[:TAG]
可以看到镜像已经推送到镜像仓库了:
下载仓库镜像:docker pull 192.168.31.54/bojiangzhou/nginx-app:1.0.0
九、图形化管理
Portainer 是一个开源、轻量级 Docker 管理用户页面,基于 Docker API,管理 Docker 主机。一般单机下不会使用图形管理页面,这里简单了解下就好。
1、安装 Portainer
① 创建数据卷:docker volume create portainer_volume
② 启动 Portainer 容器:docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_volume:/data portainer/portainer
2、访问 Portainer
访问 ip:9000,即可进入 Portainer 管理页面,首先需要创建一个管理用户
接着连接本机
连接本地进入后就可以进行容器、镜像等的管理了。
--------------------------------------------------------------------------------------------------------------------------------