应用容器引擎Docker入门
Docker简介
Docker是一个开源的应用容器引擎,基于Go语言,并遵从Apache2.0协议开源。
Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。
Docker和虚拟机的区别
虚拟机是硬件资源的虚拟化,可模拟出一台完整的计算机和操作系统。
而Docker是操作系统级别的虚拟化环境,Docker引擎的基础是Linux容器(Linux Containers,LXC)技术,虚拟环境有自己的进程和网络空间。
Docker和虚拟机并不是相互取代的关系,Docker相比较虚拟机更轻量级,资源占用更小,启动更快,使用方式更灵活。但也有它的局限性,例如由于依赖LXC技术,所以无法在Windows或macOS等其他操作系统上直接运行,不过官方提供了Docker for Windows和Docker for Mac,虽然本质上是运行在一个基于轻量级虚拟机的Linux系统上,可以理解为自动安装了一个VMWare或VirtualBox来支持Docker,不过Docker for Windows和Docker for Mac的优点是占用资源少,图形界面管理,并且与系统自带的终端完美集成,操作上与Linux版本的Docker一致。
Docker的基本原理
参考以下文章:
Docker的核心概念
Docker镜像
Docker镜像(Image)类似于虚拟机镜像,可以将它理解为一个面向Docker引擎的只读模板,包含了文件系统。
一个镜像可以只包含一个完整的Ubuntu操作系统环境,可以把它称为一个Ubuntu镜像。
镜像是创建Docker容器的基础,可以从网上下载一个已经做好的应用镜像,并通过简单的命令就可以直接使用。
Docker容器
Docker容器(Container)类似于一个轻量级的沙箱,Docker利用容器来运行和隔离应用。
容器是从镜像创建的应用运行实例,可以将其启动、开始、停止、删除,而这些容器都是相互隔离、互不可见的。
可以把容器看做一个简易版的Linux系统环境(这包括root用户权限、进程空间、用户空间和网络空间等)。
镜像自身是只读的。容器从镜像启动的时候,Docker会在镜像的最上层创建一个可写层,镜像本身将保持不变。
Docker仓库
Docker仓库(Repository)类似于代码仓库,是Docker集中存放镜像文件的场所。
目前,最大的公开仓库是Docker Hub,存放了数量庞大的镜像供用户下载。
安装Docker CE
测试环境
操作系统:Ubuntu 18.04 64-bit
卸载旧版本
较旧版本的Docker被称为docker
或docker-engine
。如果安装过,则卸载它们:
$ sudo apt-get remove docker docker-engine docker.io
较新版本的Docker被称为docker-ce
,Ubuntu上的Docker CE支持overlay2
和aufs
存储驱动程序。
- 在Linux内核版本4或更高版本上安装Docker,支持
overlay2
,并且优先于aufs
。 - 在Linux内核版本3的版本上安装Docker,支持
aufs
,因为该内核版本不支持overlay
或overlay2
。
使用apt包管理安装Docker
更新apt
包索引:
$ sudo apt-get update
安装以下软件包以允许apt
通过HTTPS使用仓库:
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
添加Docker的官方GPG密钥:
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
设置Docker仓库到apt:
$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
再更新apt包索引:
$ sudo apt-get update
安装Docker CE,默认安装最新版本(本次安装版本为18.06.1-ce
),安装完成后Docker守护进程自动启动:
$ sudo apt-get install docker-ce
如果要安装指定版本的Docker,可以先查询可用的版本:
$ apt-cache madison docker-ce
返回结果可能如下:
docker-ce | 18.06.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages docker-ce | 18.06.0~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages docker-ce | 18.03.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
选择要安装的版本,例如
18.03.1~ce3-0~ubuntu
,则在安装时指定该版本:$ sudo apt-get install docker-ce=18.03.1~ce~3-0~ubuntu
通过运行hello-world镜像验证Docker CE是否已正确安装:
$ sudo docker run hello-world
以上命令会下载测试镜像并在容器中运行它,当容器运行时,它会打印一条信息并退出。
使用国内Docker Hub源加速下载
Docker需要从Docker Hub上下载镜像,设置国内的Docker Hub源能够提升下载速度,这里使用中科大的地址,在配置文件/etc/docker/daemon.json
中配置Hub地址:
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn/”]
}
保存,重启Docker服务:
$ sudo service docker restart
验证是否生效:
$ sudo docker info
如果从结果中看到了如下内容,说明配置成功:
Registry Mirrors:
https://docker.mirrors.ustc.edu.cn/
Docker的基本用法
查询Docker信息
查询Docker信息,主要列出当前的容器数量和运行状态、镜像数量、Docker版本号、相关的资源和目录等等:
$ sudo docker info
查询Docker版本号:
$ sudo docker --verion
镜像操作
列出已下载的镜像:
$ sudo docker images
删除名称为hello-world
的镜像:
$ sudo docker rmi hello-world
从远端仓库中搜索镜像,搜索Ubuntu镜像:
$ sudo docker search ubuntu
这里选择排行第一的官方Ubuntu仓库的镜像进行下载:
$ sudo docker pull ubuntu
存出镜像到文件,ubuntu:latest
表示镜像仓库名(REPOSITORY)为ubuntu
、标签名(TAG)为latest
的镜像:
$ sudo docker save -o /tmp/ubuntu.tar ubuntu:latest
载入文件到镜像:
$ sudo docker load < /tmp/ubuntu.tar
容器操作
执行docker run
命令可以指定从某个镜像创建一个容器,-t
选项表示创建虚拟终端,-i
选项表示绑定标准输入,如果希望容器在后台运行,可以再加上-d
选项,最后指定运行/bin/bash
:
sudo docker run -it ubuntu /bin/bash
执行大概结果如下,自动进入Ubuntu容器:
root@b6bf45338f2d:/#
从容器中退出容器(容器会被关闭,因为退出了唯一的shell):
$ exit
列出已存在的容器,如不加-a
选项则只列出运行中的容器:
$ sudo docker ps -a
通过以上命令可以看到容器ID、容器所属镜像、状态、名称等信息。Docker默认会自动生成容器名称,当然也可以在创建时使用--name
选项指定容器名称:
$ sudo docker run -it --name myubuntu ubuntu /bin/bash
容器的主机名称也可以指定:
$ sudo docker run -it -h myhostname ubuntu /bin/bash
删除名称为myubuntu
的容器:
$ sudo docker rm myubuntu
重启容器,指定容器ID,容器ID可以缩写,例如b6bf45338f2d
可以缩写为b6bf4
或b6
甚至是b
,只要缩写的ID是唯一的即可:
$ sudo docker restart b6
也可以指定容器名称:
$ sudo docker restart nervous_carson
使用docker attach
进入容器:
$ sudo docker attach b6
使用docker exec
进入容器:
$ sudo docker exec -it b6 /bin/bash
attach和exec都可以进入容器,但是区别在于exec是执行命令的方式进入容器,所以如果指定执行/bin/bash,容器会新创建一个shell,这样的好处之一是执行exit退出容器时容器不会被关闭,而attach因为只有一个shell,退出后没有任何进程存在了,容器会被关闭。
终止容器:
$ sudo docker stop b6
查看容器的详细信息:
$ sudo docker inspect b6
导出容器:
$ sudo docker export b6 > /tmp/mybuntu.tar
导入myubuntu.tar,创建一个名为myubuntu的镜像:
$ sudo docker import - myubuntu < /tmp/myubuntu.tar
导出(export)导入(import)和存出(save)载入(load)都可以转储和迁移,但两者的区别在于:
export和import用于容器,会丢弃所有的历史记录和元数据信息,体积更小,相当于容器快照。
- save和load用于镜像,会保存完整记录,体积更大,相当于镜像备份。
- import可以重新指定镜像名称和标签,load不可以。
网络配置
端口映射
下载一个nginx
镜像:
$ sudo docker pull nginx
创建容器并启动,这里使用-P
选项,docker随机映射一个本地(宿主机)的端口到nginx容器的80端口:
$ sudo docker run -it -d -P nginx
执行docker ps
可以看到PORTS
列,本地的32768端口被映射到了容器的80端口:
PORTS
0.0.0.0:32768->80/tcp
这样访问宿主机的32768端口就等于是访问nginx容器的80端口,当然也可以指定宿主机要映射的端口,即使用-p
选项,映射宿主机的80端口到nginx容器的80端口 ,格式为宿主机端口:容器端口
:
$ sudo docker run -it -d -p 80:80 nginx
容器互联
查看网络列表:
$ sudo docker network ls
默认有3个网络,分别对应3种网络类型bridge
、host
和null
:
NETWORK ID NAME DRIVER SCOPE
5938cff7b158 bridge bridge local
1dd9839836f3 host host local
a5c66b5ba392 none null local
新建一个网络,类型为bridge
,名称为mynetwork
:
$ sudo docker network create -d bridge mynetwork
下载busybox
镜像,busybox
集成了三百多个最常用的Linux命令和工具:
$ sudo docker pull busybox
运行一个名称为busybox1
的容器,并连接到网络mynetwork
:
$ sudo docker run -itd --name busybox1 --network mynetwork busybox
再运行一个名称为busybox2
的容器,并连接到网络mynetwork
:
$ sudo docker run -itd --name busybox2 --network mynetwork busybox
为了验证busybox1
容器是否与busybox2
建立了互联,分别进入两个容器,并尝试ping对方:
$ sudo docker exec -it busybox1 sh
/ # ping busybox2
$ sudo docker exec -it busybox2 sh
/ # ping busybox1
结果证明两个容器已经实现互联。
数据管理
在容器中管理数据主要有两种方式:
- 卷(Volumes)
- 绑定挂载(Bind mounts)
卷
创建一个名称为my-vol
的卷:
$ sudo docker volume create my-vol
查看所有卷:
$ sudo docker volume ls
查看卷的详细信息,可以看到卷在宿主机的挂载点:
$ sudo docker volume inspect my-vol
删除卷:
$ sudo docker volume rm my-vol
删除未使用的卷:
$ sudo docker volume prune
启动一个容器,并使用--mount
选项指定挂载一个卷my-vol2
到容器的/test
目录,启动容器时如果指定的卷不存在会自动创建:
$ sudo docker run -itd --name ubuntu --mount source=my-vol2,target=/test ubuntu
这样在容器的/test目录下的数据,在宿主机或其他挂载了卷my-vol2
的容器都可以共享和访问。重要的一点是即使容器被删除,卷里的数据仍然存在。
绑定挂载
启动一个容器,挂载主机目录/tmp
到容器的/test
目录,挂载类型为bind
,挂载的主机目录必须已存在:
$ sudo docker run -itd --name ubuntu2 --mount type=bind,source=/tmp,target=/test ubuntu
以上命令也可以简写为:
$ sudo docker run -itd --name ubuntu2 -v /tmp:/test ubuntu
使用卷还是绑定挂载
两者都可以实现容器数据的持久化和共享,但是绑定挂载有明显的缺陷:
- 容器必须依赖于一个存在的主机目录,会影响容器可移植性。
- 在多个容器使用相同主机目录的情况下容易发生冲突。
- 容器可以直接使用、修改主机目录文件,存在安全隐患。
而卷算得上是对绑定挂载的改良,卷是由Docker管理,并与主机的核心区域隔离。
所以,应该尽可能的优先使用卷,而不是绑定挂载。
定制镜像
使用commit定制镜像
镜像是多层存储结构,而容器同样也是多层存储结构,容器是以镜像为基础层,加一层作为容器运行时的存储层。
我们在容器里做的任何文件修改都会被记录在容器的存储层里(卷除外),可以通过docker diff
命令看到具体的改动。
当我们定制好了变化,我们希望能将其保存下来形成镜像,Docker提供了docker commit
命令,可以将容器的存储层保存下来成为镜像,我们试着将名称为ubuntu
的容器保存为名称为myubuntu
、标签为v1
的镜像:
$ sudo docker commit ubuntu myubuntu:v1
使用docker images
就可以看到这个新定制的镜像。
可以查看这个镜像的历史记录:
$ sudo docker history ubuntu-test:v1
docker commit
虽然可以用来实现定制镜像,但鉴于以下几点原因,应该谨慎使用:
- 对镜像的操作都是黑箱操作,制作镜像时执行过什么命令,怎么生成的无从得知。
- 虽然
docker diff
可以得到一些线索,但远远不够。 - 因为是分层存储的结构,上一层的存储无法修改,在当前层删除上一层产生的文件,并不是真正被删除,而是被标记掉,这就导致每一次用commit制作镜像都会使镜像更加臃肿。
使用Dockerfile定制镜像
Dockerfile是一个文本文件的脚本,包含一条条的指令,一条指令构建一层,描述了该层应当如何构建。
Dockerfile解决了使用docker commit
所带来的问题,下面说明如何编写和使用Dockerfile来定制镜像。
首先创建一个目录,新建一个Dockerfile文件:
$ mkdir myubuntu
$ cd myubuntu
$ touch Dockerfile
编辑Dockerfile,内容为:
FROM ubuntu
RUN mkdir /testdir
RUN echo 'Dockerfile test' > /testdir/testfile
这个Dockerfile很简单,FROM ubuntu
表示是以ubuntu镜像为基础进行定制
如果不想以任何镜像为基础,可以使用
FROM scratch
,scratch表示一个空白的镜像。
RUN
指令是用来执行命令,这里我们创建了一个 /testdir
目录,并在该目录下创建了一个/tmp/test
文件。
Dockerfile中每一个指令都会建立一层,上面的Dockerfile有2个RUN
指令,因此创建了2层,但由于分层存储是有层数限制的,所以应该减少不必要的层数,Dockerfile可以优化改写为:
FROM ubuntu
RUN mkdir /testdir \
&& echo 'Dockerfile test' > /testdir/testfile
然后就可以构建这个镜像了,在Dockerfile文件所在目录执行(请注意结尾有一个.
,它的作用是将当前目录作为构建的上下文):
$ sudo docker build -t myubuntu:v2 .
使用docker images
就可以看到这个新定制的镜像。
除了FROM
和RUN
指令之外,docker还提供其他一些很有用的指令,比如COPY
、ADD
、CMD
、ENTRYPOINT
等等,这里暂不详细说明。
创建一个基础镜像
大多数情况下都可以在Docker Hub中找到我们需要的基础镜像,或者利用Dockerfile对基础镜像进行定制,但如果我们想创建一个全新的镜像,就需要费点功夫去打包一个Linux操作系统,具体如何打包取决于不同的Linux发行版,以Ubuntu举例,Ubuntu的打包比较简单。
我们准备一个虚拟机,已安装了Ubuntu,在这个虚拟机里先安装debootstrap
:
$ sudo apt install debootstrap
debootstrap是debian/ubuntu下的一个工具,用来构建一套基本的系统(根文件系统)。
使用debootstrap
构建一个基本的系统,命令格式大致如下:
$ sudo debootstrap --arch=amd64 [版本代号] [下载目录] [指定软件源]
执行命令:
$ sudo debootstrap --arch=amd64 bionic bionic http://mirrors.aliyun.com/ubuntu
执行后,debootstrap
就会开始下载一个Ubuntu 18.04的基本系统到当前目录下的bionic目录,bionic
是Ubuntu 18.04的版本代号。
可以chroot到这个基本系统,做一些操作,安装一些软件包之类的:
$ sudo chroot bionic /bin/bash
操作完成清理系统并退出
# rm -Rf /tmp/* && apt clean
# exit
使用tar
进行打包:
$ sudo tar --numeric-owner -cvf bionic.tar -C bionic .
最后使用docker import
就可以导入成镜像:
$ sudo cat bionic.tar | sudo docker import - [镜像名]