什么是Docker?
Docker是由dotcloud
公司使用golang
语言进行开发的,基于Linux
内核的 cgroup
,namespace
,以及OverlayFS
类的Union FS
等技术,对进程进行封装隔离,属于操作系统
层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。
下面的图片比较了 Docker 和传统虚拟化方式的不同之处:
传统虚拟化
Docker
传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
为什么要使用Docker
更高效地利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker
对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。
更快速的启动时间
传统虚拟机在启动的时候是需要启动一个完整的操作系统,所以使用传统虚拟化技术启动一个服务需要数分钟,而Docker
容器应用是直接将服务的应用程序启动在宿主机的内核上,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。
一致的运行环境
开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些bug
并未在开发过程中被发现。而Docker
的镜像提供了除内核以外的完整的运行时环境,确保了应用运行环境一致性,不需要做任何配置就能在任意Linux服务器上运行,即Build Once, Run Anywhere
。从而不会再出现「这段代码在我机器上没问题啊」这类问题。
更轻松的迁移与弹性伸缩
由于Docker
镜像封装了除操作系统内核外的完整的文件系统,所以Docker应用在进行迁移的时候只需要在新的服务器上将镜像拉取下来或者使用Dockerfile重新Build
,然后run
一下就迁移完成了,完全不用担心运行环境的变化导致应用无法正常运行的情况。
更轻松的扩展与维护
Docker
使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。
与传统虚拟技术对比总结
特性 | 容器 | 虚拟机 |
---|---|---|
启动速度 | 秒级 | 分钟级 |
磁盘使用 | MB级 | GB级 |
单机运行数量 | 支持上千个容器 | 最多跑几十个虚拟机 |
Docker基础概念
镜像(Image)
我们都知道,操作系统分为内核和用户空间。对于Linux
而言,内核启动后,会挂载 root
文件系统为其提供用户空间支持。而 Docker镜像(Image)
,就相当于是一个 root文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。
Docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
容器(Container)
容器(Container)
与镜像(Image)
的关系,就像是面向对象程序设计中的类
和实例
一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的root
文件系统、自己的网络配置、自己的进程空间,甚至自己的用户ID
空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。
仓库(Registry)
Docker Registry提供的是保存Docker镜像的服务,便于将镜像可以在任意支持Docker引擎的服务器上“实例化”为容器提供服务。
通常一个Registry可以包含多个仓库,每个仓库里又可以包含多个镜像,每个镜像之间使用tag(标签)
进行区分,如果镜像没有标签则以默认latest
作为该镜像的标签,相同标签的镜像会被新镜像覆盖。
以ubuntu镜像为例,ubuntu是仓库的名字,其内包含有不同的版本标签,如,16.04, 18.04。我们可以通过ubuntu:16.04
,或者ubuntu:18.04
来具体指定所需哪个版本的镜像。如果忽略了标签,比如ubuntu
,那将视为ubuntu:latest
。
Docker基础命令
01. 使用镜像
获取镜像
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。
- Docker 镜像仓库地址:地址的格式一般是
<域名/IP>[:端口号]
。默认地址是Docker Hub(docker.io)
。 - 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。
例如:
docker pull ubuntu:15.10
列出镜像
docker image ls
删除镜像
docker image rm <镜像id>
docker images rm <镜像名>:<镜像tag>
# 例如:
docker images rm 5c64
docker images rm alpine:latest
02. 运行容器
使用docker run命令启动一个ubuntu的容器并在其中输出 Hello World文本,执行完毕后,容器会自动退出。
docker run --name=hello ubuntu:15.10 /bin/echo "Hello, World"
参数解析:
run: y与docker组合启动一个新容器;
--name: 容器的名称;
ubuntu:15.10:启动容器使用的镜像;
/bin/echo:在容器中执行的命令。
03. 运行交互式容器
使用命令行运行以下命令,此命令将启动一个ubuntu容器并在其中运行bash交互命令行界面,你可以尝试运行pwd,ls,ps等命令查看容器内环境,就如同远程操作一台服务器一样。
docker run -i -t ubuntu:latest /bin/bash
root@cd07977c1a22:/#
-i: 以交互模式运行容器,通常与 -t 同时使用;
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
第二行表示已经进入了一个ubuntu系统的容器中,可以执行命令。
04. 在容器中运行持续任务并管理容器生命周期
后台启动容器
使用以下命令创建一个以进程方式运行的容器
docker run --name=ubuntu -it -d ubuntu:15.10 /bin/bash
参数解析:
-d: 后台运行容器,并返回容器ID;
/bin/bash: 运行的命令,必须是可以一直挂起的命令,如死循环。
docker run --name=test -d ubuntu:15.10 sh -c "while true; do echo hello world; sleep 1; done"
Docker容器后台运行,就必须有一个前台进程,容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的
查看容器列表
使用docker ps命令查看当前宿主机上运行的容器:
# 查看正在运行的容器
docker ps
# 查看所有容器
docker ps -a
查看容器日志
使用docker logs命令查看容器日志
# 查看容器日志
docker logs <containerid>
# 实时查看容器日志
docker logs -f <containerid>
连接容器
连接容器有两种方法,attach
与exec -it
:
# 使用attach
docker attach <containerid>
# 使用exec -it
docker exec -it <containerid> /bin/<bash|sh>
注意事项:
使用
attach
连接是使用现有终端,ctrl+d退出后容器也跟着退出,需要使用ctrl+p+q;
使用exec -it
是使用伪终端,ctrl+d退出后容器会继续运行。
停止及删除容器
停止运行中的容器
docker stop e8c6
删除容器
# 删除已经停止的容器
docker rm e8c6
# 删除正在运行的容器
docker rm -f e86c
删除所有停止的容器
docker container prune
容器重启
启动已经停止的容器
docker start e8c6
重启正在运行的容器
docker restart e8c6
05. 容器端口映射
将容器中服务端口映射到宿主机:
# 指定宿主机端口映射
docker run --name=nginx -d -p 80:80 nginx:1.17.0
# 随机宿主机端口映射
docker run --name=nginx -d -P nginx:1.17.0
-p:将容器目录映射至宿主机,<宿主机端口>:<容器端口>
-P: 将容器中暴露的端口映射到宿主机上的随机端口,防止端口冲突。
06. 容器与宿主机文件传输
目录挂载
# 默认权限为读写
docker run --name=nginx -d -v /data:/data nginx:latest
# 配置权限为只读
docker run --name=nginx -d -v /data:/data:ro nginx:latest
文件cp
使用docker cp命令传输文件
# 将宿主机文件copy至容器
docker cp test.txt nginx:/mnt/
# 将容器文件copy至宿主机
docker cp nginx:/mnt/test.txt /mnt/
07. docker for mac中容器访问宿主机服务
docker在mac上启动后,在宿主机上没有docker-0网桥,所以无法通过访问宿主机ip或者docker-0网桥ip访问宿主机服务。
docker for mac提供了一个宿主机的域名host.docker.internal
,所以mac*问宿主机业务时的格式为host.docker.internal:port
,如settings.py中mysql的配置:
MYSQL_HOST = 'host.docker.internal'
MYSQL_PORT = '3306'
nginx容器反向代理宿主机后端服务时的配置:
proxy_pass http://host.docker.internal:8899;