一、概述
1、容器为解决什么问题
现在的软件系统已经非常复杂。一方面包含多种服务,这些服务有自己所依赖的库和软件包;另一方面存在多种部署环境。这就产生了一个问题:如何让每种服务能够在所有的部署环境中顺利运行?
容器的设计理念由码头上的集装箱而来,通过集装箱的标准化
、相互隔离
实现软件系统依赖的打包与运行隔离。
2、Docker是什么
Docker
现在几乎是容器的代名词,它同时也是Docker公司
的名字。不过我们通常所说的Docker
指的是容器
或者容器技术
。
使用Docker
之前,需要安装它。以CentOS
为例:
# yum install docker-ce
# systemctl start docker
之后便可以使用docker命令操作容器或镜像了(容器与镜像下文会讲到)。
3、容器优点
- 一致的运行环境:Docker的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性;
- 极其轻量:只打包了必要的Bin/Lib,容器不是模拟一个完整的操作系统,而是对进程进行隔离,是进程级别的;
- 秒级部署:根据镜像的不同,容器的部署大概在毫秒与秒之间(比虚拟机强很多);
- 易于移植:一次构建,随处部署;
- 弹性伸缩:Kubernetes、Swam、Mesos这类开源、方便、好使的容器管理平台有着非常强大的弹性管理能力。
4、容器的主要应用场景
- 持续集成和持续部署(CI/CD): 通过
Docker
完成持续集成管道自动化和应用部署; - 微服务;
二、Docker中的概念及使用
1、镜像(Image)
Docker镜像是Docker容器运行的基础,没有Docker镜像,就没有Docker容器。镜像与容器就像是面向对象程序设计中的类
和实例
一样,镜像是静态的定义,容器是镜像运行时的实体
镜像是一个特殊的文件系统,除了提供容器运行时所需的程序
、库
、资源
、配置
等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。
(1) 镜像的获取方式通常有3种:
1、 通过Dockerhub
提供;
2、 通过某一个已经运行的容器生成镜像(下文讲);
3、 使用Dockerfile
生成(下文讲);
上面是使用pull
子命令从DockerHub
下载centos:latest
镜像,其中centos
是镜像名,latest
是标签名。使用docker images
命令已经可以看到刚下载的centos镜像了。
(2) 镜像常用操作
从仓库拉取镜像: # docker pull <image_id>
有时候可能需要先登录docker login <registry_host>
从容器生成镜像: # docker commit <container_id> <image_name>
删除镜像: # docker rmi [-f] <image_id | image_name>
转移镜像: # docker save <image_id | image_name> a_file_name.tar
保存镜像到文件 # docker load a_file_name.tar
加载镜像文件到本地镜像库
2、容器(Container)
容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。 容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。
(1) 启动容器
Docker启动容器的命令格式:Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
,现在使用我们下载的centos镜像启动一个容器:
上图使用
run
命令启动一个容器(注意容器启动前后的whoami输出
以及终端提示符
的变化)。
-ti
会进入容器的终端(使用ctrl-p
+ctrl-q
方式返回宿主机);-name test
容器名以test命令,不指定会随机去一个字符串;centos:latest
启动容器使用的镜像名;/bin/bash
容器启动后的执行命令;
除了这里的几个启动参数外,启动容器还经常使用以下OPTIONS
:
--privileged=true
进入容器后,使用真正的root权限;--net host
表示在容器内与主机共享网卡,host
是安装Docker时在主机上创建的三个网络之一;--volume host_dir:container_dir
把容器内的container_dir
挂载到宿主机的host_dir
目录;--env 环境变量名=value
设置容器内的环境变量;--cpu-shares n
设置容器占用CPU的权重n,这个权重是相对的,第一个容器是10,第二个是20,那第二个容器占用的CPU就是第一个的2倍;-p host_port:container_port/udp
宿主机与容器的端口映射,默认是tcp,如果是udp需要/udp
;
(2) 容器基本操作
启动容器: # docker run [OPTIONS] <image_name | image_id> [COMMAND] [ARG...]
查看容器信息: 使用inspect
子命令查看容器/镜像的元数据。包括id
、启动命令
、网络连接方式
、端口映射
等信息。
进入容器: # docker exec -it <container_name | container_id>
停止容器: # docker stop/kill <container_name | container_id>
删除容器: # docker rm [-f] <container_name | container_id>
3、Dockerfile
虽然DockerHub
提供了众多Linux镜像,但如果需要定制,DockerHub
还是不能满足的。这个时候就需要基于DockerHub
提供的镜像做一些扩展。
Dockerfile
(1) 常用关键字
FROM
使用一个基础镜像构建;RUN
执行一个shell命令,通常用来安装工具;ENV
设置容器内的环境变量;COPY
拷贝宿主机文件到镜像内;ADD
拷贝宿主机文件到镜像内,并解压;WORKDIR
设置容器启动时的工作目录,默认会cd到该目录,如果目录不存在会自动创建;
(2) 两个特殊的关键字
1、 CMD
CMD
指令允许用户指定容器启动的默认执行的命令。此命令会在容器启动且docker run
没有指定其他命令时运行。
- 如果
docker run
指定了其他启动命令,CMD
指定的默认命令将被忽略;否则默认执行CMD
指定的命令; - 如果
Dockerfile
中有多个CMD
指令,只有最后一个CMD
有效;
CMD
的三种格式(不只是CMD
可以使用,RUN
和ENTRYPOINT
也可以使用这三种方式):
1、 以/bin/bash -c
的方法执行命令。比如CMD /bin/bash -c "echo hello world"
;
2、 运行一个可执行文件并提供参数,称为Shell方式。比如CMD echo "hello world"
;
3、 可执行文件连同参数通过一个数组提供,成为Exec方式,比如CMD ["echo", "hello world"]
;
1、 ENTRYPOINT ENTRYPOINT
与CMD
类似,都是指定容器的启动参数。不同之处在于:
ENTRYPOINT
一定会执行,不会被忽略;CMD
指令允许用户指定容器启动的默认执行的命令。此命令会在容器启动且docker run
没有指定其他命令时运行;
当ENTRYPOINT
使用exec方式时,还可以通过使用CMD
的exec方式提供额外的参数,此时CMD
的参数列表仅包含参数、不再有可执行文件。比如:
- 下面的Dockerfile片段,
ENTRYPOINT ["echo", "hello"] CMD ["world"]
,会输出hello world
; - 若启动上面的Dockerfile创建的镜像,启动命令为
docker run -it [image] cvte
,会输出hello cvte
;
(3) 构建镜像
# docker build -t tag_name path_to_Dockerfile
Tips:
Dockerfile
所在目录除了需要拷贝到镜像中的文件外,不要有其他无关文件或文件夹,即保证Dockerfile
所在目录干净
。否则构建时可能会出现长时间拷贝导致失败;
(4) 一个例子
4、Registry
Registry
是存放Docker镜像
的仓库,分公有和私有两种。
(1) DockerHub
DockerHub
是Docker公司
对公众提供的免费Registry,用户可以在上面下载各种类型的应用或镜像。比如下载一个有Python3环境的镜像、可以提供redis存储服务的服务器。官网地址
(2) 公司内网
出于对速度或安全的考虑,用户可以创建自己私有的Registry。公司内网Registry地址
三、Docker管理
除了上面介绍的对容器和镜像的基本操作外,还可以使用docker-compose
管理容器。
docker-compose
通过一个docker-compose.yml
配置文件,完成单个容器的配置(比如镜像、端口映射、目录挂载、环境变量等)、多个相互依赖的容器编排(容器启动顺序)等功能。
这块内容较多并且也不难,有了上文的基础,通过下文的参考文档应该能掌握。
四、实现容器的底层技术
cgroup
和namespace
是实现容器的两个重要技术。cgroup
实现资源限额,namespace
实现资源隔离。
1、cgroup
:
- 对容器的资源限制,比如–cpu-shares、-m实际就是在配置cgroup;
- 在/sys/fs/cgroup/cpu/docker目录中,会为每个容器创建一个cgroup目录,以容器长ID命名,里面的若干文件就是与CPU相关的cgroup配置。比如cpu.shares保存的就是–cpu-shares的配置;
- 同理,/sys/fs/cgroup/memory/docker保存的就是容器内存的cgroup配置;
2、namespace
:
namespace相对cgroup要难一些。Linux使用了6中namespace,分别对应6种资源:Mount、UTS、IPC、PID、Network、User。
- Mount namespace: 让容器看山去拥有整个文件系统,可以在容器内执行mount,这些操作只会在容器内生效;
- UTS namespace:让容器拥有自己的hostname。默认情况下,容器的hostname是它的短ID,也可以使用-h或–hostname指定。
- IPC namespace:让容器拥有自己的IPC来实现进程间通信,不会与主机的IPC混合在一起;
- PID namespace:容器在宿主机上以进城的形式存在;所有容器进城都挂载dockerd进程下,同时也可以看到容易内只属于该容器的子进程;
- Network namespace:让容器拥有自己独立的网卡、IP、路由等资源;
- User namespace:让容器能够管理自己的用户,host不能看到容器中创建的用户。
参考文档: Docker–从入门到实践