Docker provides a way to run applications securely isolated in a container, packaged with all its dependencies and libraries.
Docker简介
什么是容器
- 一种虚拟化的方案
- 操作系统级别的虚拟化
- 只能运行相同或相似内核的操作系统
- 依赖于Linux内核特性: Namespace和Cgroups(Control Group)
Docker依赖的Linux内核特性
- Namespace 命名空间
- 编程语言
- 封装 代码隔离
- 操作系统
- 系统资源的隔离
- 进程, 网络, 文件系统
- 编程语言
- Control groups 控制组
- 用来分配资源
Docker容器的能力
- 文件系统隔离: 每个容器都有自己的root文件系统
- 进程的隔离: 每个容器都运行在自己的进程环境
- 网络的隔离: 容器间的虚拟网络接口和IP地址都是分开的
- 资源隔离和分组: 使用cgroups将CPU和内存之类的资源独立分配给每个Docker容器
什么是Docker
- 将应用程序自动部署到容器的开源引擎
Docker的目标
- 提供简单轻量的建模方式
- 指责的逻辑分离
- 快速高效的开发生命周期
- 鼓励使用面向服务的架构
Docker的使用场景
- 使用Docker容器开发, 测试, 部署服务
- 创建隔离的运行环境
- 搭建测试环境
- 构建多用户的平台即服务(PaaS)基础设施
- 提供软件即服务(SaaS)应用程序
- 高性能, 超大规模的宿主机部署
Docker的基本组成
- Docker Client 客户端
- Docker Daemon 守护进程
- Docker Image 镜像
- Docker Container 容器
- Docker Registry 仓库
Docker的安装和配置
Docker在Ubuntu中的安装
安装前检查:
- 内核版本
1 | uname -a |
- 检查Device Mapper
1 | ls -l /sys/class/misc/device-mapper |
Ubuntu中安装Docker的方式
- 安装Ubuntu维护的版本
1 | sudo apt-get install docker.io |
- 安装Docker维护的版本
1 | sudo apt-get install -y curl |
使用非root用户:
1 | sudo groupadd docker |
Docker在Windows中的安装
Docker在OS X中的安装
Docker容器
容器的基本操作
- 启动容器:
1 | docker run IMAGE [COMMAND] [ARG...] |
run 在新容器中执行命令 eg.docker run ubuntu echo ‘Hello World’
- 启动交互式容器
1 | docker run -i -t IMAGE /bin/bash |
- 查看容器:
1 | docker ps [-a] [-l] |
- 自定义容器名:
1 | docker run --name=自定义名 -i -t IMAGE /bin/bash |
- 重新启动停止的容器:
1 | docker start [-i] 容器名 |
- 删除停止的容器:
1 | docker rm 容器名 |
守护式容器
-
什么是守护式容器
- 能够长期运行
- 没有交互式会话
- 适合运行应用程序和服务
-
以守护形式运行容器:(将一个交互式的容器转到后台)
1 | docker run -i -t IMAGE /bin/bash |
- 附加到运行中的容器:
1 | docker attach 容器名 |
- 启动守护式容器:
1 | docker run -d 镜像名 [COMMAND] [ARG...] |
eg. docker run --name dc1 -d ubuntu /bin/sh -c “while true; do echo hello world; sleep 1; done”
- 查看容器日志:
1 | docker logs [-f] [-t] [--tail] 容器名 |
- 查看容器内进程:
1 | docker top 容器名 |
- 在运行中的容器内启动新进程:
1 | docker exec [-d] [-i] [-t] 容器名 [COMMAND] [ARG...] |
eg. docker exec -i -t dc1 /bin/bash
- 停止守护式容器:
1 | docker stop 容器名 |
stop: 发送一个信号给容器,等待容器的停止
kill: 直接停止容器
在容器中部署静态网站
由于网站服务通常是通过80端口服务的, 为了访问容器的80端口, 需要在运行容器时设置容器的端口映射
1. 设置容器的端口映射
1 | run [-P] [-p] |
2. Nginx部署流程
- 创建映射80端口交互式容器
- 安装Nginx
- 安装文本编辑器vim
- 创建静态页面
- 修改Nginx配置文件
- 运行Nginx
- 验证网站访问
1 | # 运行带端口映射的容器 |
Docker镜像
Docker镜像是一种使用联合加载(union mount)技术实现的层叠的只读文件系统,它是容器构建的基石。
查看和删除镜像
镜像的存储地址
docker info指令查看
1 | sudo ls -l /var/snap/docker/common/var-lib-docker/aufs |
列出镜像
1 | docker images [OPTIONS] [REPOSITORY] |
- -a
- -f
- -no-trunc
- -q 只返回id
镜像标签和仓库
- REPOSITORY 仓库
- 是一系列镜像的集合
- REGISTRY 仓库
- 这个是之前提到过的docker组建中的仓库,它是提供的docker镜像的存储服务,从这里看出REGISTRY仓库包含了很多REPOSITORY的仓库,而REPOSITORY的仓库中包含了一个个独立的镜像
- TAG 镜像的标签
- REPOSITORY + TAG 构成一个完整的镜像名,这个镜像名对应一个完整的ID
- ubuntu:14.04
- ubuntu:latest
同一个仓库的不同标签可能对应相同的ID,也就是根据我们的需求打上不同的标签
1 | # 显示完整的镜像ID |
查看镜像
docker inspect : 获取容器/镜像的元数据
1 | docker inspect [OPTIONS] NAME|ID [NAME|ID...] |
删除镜像
1 | docker rmi [OPTIONS] IMAGE [IMAGE...] |
获取和推送镜像
查找镜像
-
Docker Hub
-
docker search [OPTION] TERM
- -=automated=flase
- –no-trunc=false
- -s
1 | ▶ docker search ubuntu |
拉取镜像
- docker pull [OPTIONS] NAME [:TAG]
1 | docker pull ubuntu:14.04 |
推送镜像
- docker push NAME[:TAG]
可在docker hub上访问上传的镜像
构建镜像
- 保存对容器的修改,并再次修改
- 自定义镜像的能力
- 以软件的形式打包并分发服务及其运行环境
也就是说,我们可以通过自定义的镜像,将服务与运行的系统及其软件运行环境通过镜像打包在一起,那么在运行其他docker守护进程的主机上都可以以这个镜像运行容器从而提供服务。
- docker commit 通过容器构建
- docker build 通过dockerfile文件构建
使用commit构建镜像
- docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
- -a 作者
- -m 作者的信息
- -p 由于执行commit命令时会将正在执行的容器暂停,-p可以指示不暂停正在执行的容器
1 | # 启动容器 |
使用dockerfile构建镜像
- 创建dockerfile
- 使用docker build命令
eg.dockerfile
1 | # First Dockerfile |
- docker build [OPTIONS] PATH | URL | -
- -f :指定要使用的Dockerfile路径
- –tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签
1 | # docker build -t='镜像名字' dockerfile地址(这里就是当前目录) |
Docker的CS模式
前面提到过,docker是以客户端和守护进程的方式运行,在客户端中运行各种命令,这些命令会传递给在docker的宿主机上运行的docker的守护进程,而docker的守护进程是负责实现docker的各种功能
上面这幅图显示,docker的客户端运行在宿主机上,也就是C/S架构的server端,守护进程会在启动后在后台运行,负责实现docker的各种功能,而docker的使用者并不会直接与守护进程进行交互,而是通过docker的客户端。
docker的命令行接口是docker最主要的客户端接口,docker也提供了另外的与守护进程通信的方式,这就是remote API
Remote API
- RESTful 风格API
- STDIN,STDOUT,STDERR
这幅图可以看出,通过Remote API的形式来实现docker的C/S架构模式,用户通过与自定义的程序交互,这个程序通过调用Remote API与docker守护进程协作
连接方式
docker的客户端与守护进程的连接:socket
- unix://var/run/docker.sock
- tcp://host:post
- fd://socketfd
docker的客户端与docker的服务器端通过socket进行连接,这种连接本身也就意味着docker的客户端与服务端既可以在同一台机器上运行也可以在不同的机器上运行,也就是docker的客户端可以通过远程的方式来访问docker的服务端
Docker守护进程的配置和操作
查看docker守护进程的运行状态
1 | ps -ef | grep docker |
启动,停止,重启docker守护进程
使用service命令管理
1 | sudo service docker start |
docker的启动选项
- docker -d [OPTIONS]
OPTIONS中,有运行相关的,有docker服务器连接相关的,有RemoteAPI相关的,有存储相关的,有Registry相关的,有网络设置相关的(DNS服务器配置以及容器ip的配置)
修改docker守护进程的启动配置
这里必须要说的是docker的启动配置文件:/etc/default/docker
,在这个配置文件中,可以设置docker启动时运行的各种选项
Dockerfile
Dockerfile其内部包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建.
Dockerfile结构大致分为四个部分:
- 基础镜像信息
- 维护者信息
- 镜像操作指令
- 容器启动时执行指令
CMD和ENTRYPOINT指令用来指定容器启动时执行的命令,ADD,COPY,VOLUME指令用来设定镜像的目录和文件,WORKDIR,ENV,USER指令用来指定镜像构建及容器运行时的环境设置,还有ONBUILD指令这里一个类似触发器的指令
FROM指令
- FROM < image >
- FROM < image >:< tag >
- 已经存在的镜像
- 基础镜像
- 必须是第一条非注释指令
MAINTAINER指令
- MAINTAINER < name >
- 指定镜像的作者信息,包含镜像的所有者和联系信息
RUN指令
- 指定当前镜像中运行的命令
- RUN < command > (shell模式)
- /bin/sh -c command
- eg. RUN echo hello
- RUN [ “executable”, “param1”, “param2” ] (exec模式)
EXPOSE指令
- EXPOSE < port > [ < port > … ]
- 指定运行该容器镜像的容器使用的端口
CMD指令
- CMD [ “executable”, “param1”, “param2” ] (exec模式)
- CMD command param1 param2 (shell模式)
- CMD [ “param1”, “param2” ] (作为ENTRYPOINT指令的默认参数)
- 用来提供容器运行的默认命令,与RUN类似都是执行一个命令,但是RUN指定的命令是在镜像构建中执行的,而CMD指定的命令是在容器运行时运行的,且当使用docker run命令启动一个容器时,如果指定了容器运行时的命令,那么CMD中指定的指令会被覆盖,也就是说CMD指令用来指定容器的默认行为
ENTRYPOINT指令
- ENTRYPOINT[ “executable”, “param1”, “param2” ] (exec模式)
- ENTRYPOINT command param1 param2 (shell模式)
- 可以使用docker run -entrypoint覆盖(这也是CMD与ENTRYPOINT指令的不同之处)
CMD指令与ENTRYPOINT指令组合使用时,用ENTRYPOINT指定命令,用CMD指定参数即可
1 | # First Dockerfile |
ADD指令
- ADD < src >…< dest >
- ADD “< src >”…“< dest >”
- ADD和COPY指令非常相似,都是将文件和目录复制到使用dockerfile构建稍微镜像中,都支持2种参数:来源地址和目标地址,文件和目录的来源可以是本地地址,也可以是远程的url,如果是本地地址必须是构建目录中的相对地址,对于远程url,docker不推荐使用,建议使用wget获取文件,而目标路径需要指定镜像中的绝对路径
COPY指令
- COPY < src >…< dest >
- COPY “< src >”…“< dest >”
- ADD和COPY指令不同之处:ADD包含类似tar的解压功能,如果单纯复制文件,docker推荐使用COPY
1 | # First Dockerfile |
VOLUME指令
- VOLUME [“/data”]
- VOLUME指令基于镜像创建的容器添加卷
WORKDIR指令
- WORKDIR /path/to/workdir
- WORKDIR指令用来从镜像创建一个新容器时,在容器内部设置工作目录,CMD与ENTRYPOINT指定的目录都会在这个目录下执行,也可为后续指令指定工作目录
- WORKDIR通常会使用绝对路径,如果使用了相对路径,那么工作路径会一直传递下去
ENV指令
- ENV < key > < value >
- ENV < key >=< value >…
- 用来设置环境变量,与WORKDIR指令类似
- 环境变量的指令也可以作用于构建过程中以及运行过程中同样有效
USER指令
- USER daemon
- USER指令用来指定镜像为什么样的用户去运行 eg. USER nginx
- 也可使用:
- USER user
- USER user:group
- USER user:gid
- USER uid
- USER uid:gid
- USER uid:group
- 不指定默认root用户
ONBUILD指令
- ONBUILD [INSTRUCTION]
- ONBUILD指令为镜像添加触发器,当一个镜像被用于其他镜像的基础镜像时,这个触发器会被执行,当子镜像在构建时会插入触发的指令
Dockerfile构建过程
- 从基础镜像运行一个容器
- 执行一条指令,对容器做出修改
- 执行类似docker commit的操作,提交一个新的镜像层
- 在基于刚提交的镜像运行一个新容器
- 执行dockerfile中的下一条指令,直至所有指令执行完毕
docker build命令会删除中间层创建的容器,但是没有删除中间层创建的镜像,也就是说,可以使用docker run命令使用中间层镜像运行一个容器,从而查看每一步构建后镜像的实际状态,使用中间层镜像进行调试的好处就是查找错误
接下来学习构建过程中另一个重要概念:构建缓存
由于每一步的构建过程都会将结果提交成一个镜像,所以docker构建镜像会将之前的镜像看作缓存
但是有些情况下不想使用缓存,比如构建过程中包含apt-get update命令时,我们希望每一次docker都刷新apt-get包的缓存,这样就能得到最新的版本
跳过缓存的指令:
1 | docker build --no-cache |
查看镜像构建的过程:
1 | docker history [image] |
Docker容器的网络连接
Docker容器的网络基础
docker0
开始前先用ifconfig指令看一下运行docker守护进程机器上的网络设备:
1 | ifconfig |
系统中存在一个docker0的网络设备,docker守护进程就是通过docker0为docker的容器提供网络连接的各种服务。
docker0实际上就是Linux的虚拟网桥,那什么是网桥呢?
根据OSI七层模型,网桥是数据链路层的一种设备,它用来通过MAC地址来对网络进行划分,并且在不同的网络直接传递数据。
Linux的虚拟网桥有一些不同的特点:
- 可以设置IP地址
- 相当于拥有一个隐藏的虚拟网卡
当Linux的虚拟网桥拥有IP后,Linux就可以通过路由表或IP表规则在网络层定位网桥,这相当于拥有一个隐藏的虚拟网卡,而这个网卡的名字就是这个虚拟网桥的名字,也就是刚才看到的docker0
docker0的地址划分:
- IP:172.17.0.1 子网掩码:255.255.0.0
- MAC:02:42:ac:11:00:00 到 02:42:ac:11:ff:ff
- 总共提供了65534个地址
docker守护进程在一个容器启动时,实际上它要创建网络连接的两端:一端是在容器中的网络设备,而另一端是在运行docker守护进程的主机上,打开一个名为veth*的接口,用来实现docker0这个网桥与容器的网络通信
在ubuntu上,首先要安装网桥管理工具:
1 | sudo apt-get install bridge-utils |
然后查看网桥设备:
1 | sudo brctl show |
这里可以看到有一个docker0的网络设备,interfaces里面的接口就是docker在容器创建时,为容器连接docker0所创建的一个网络接口,同样的ifconfig命令也可以查看这个网络接口:
1 | ifconfig |
自定义docker0
当docker0所提供的默认ip地址范围不能满足我们期望为容器分配的ip地址时,可以通过修改docker0地址,将它改为我们期望使用的网段:
1 | # 假设新地址为192.168.200.1 |
再查看新容器的ip地址后会看到已经修改为所期望的地址
自定义虚拟网桥
在有些情况下,我们不希望docker默认提供的docker0这个虚拟网桥,我们希望自己建立新的虚拟网桥来给docker使用,这样也是可以实现的,需要以下两个步骤:在系统中建立一个虚拟网桥,然后在docker守护进程的启动配置中添加对新的虚拟网桥使用的定义
添加虚拟网桥:
1 | sudo brctl addbr br0 |
更改docker守护进程的启动配置:
- /etc/default/docker 中添加DOCKER_OPS值
- -b=br0
- 保存退出后重启docker守护进程
最后查看是否成功配置:
1 | ps -ef | grep docker |
Docker容器的互联
- 允许所有容器互联
- 拒绝容器间互联
- 允许特定容器间的连接
Docker容器与外部网络的连接
- ip_forward
- iptables
- 允许端口映射访问
- 限制ip访问容器
ip_forward
1 | --ip-forward=true |
ip_forward默认值为true,当使用这个默认值时,docker会在守护进程启动时将系统的ip_forward设置为1,也就是允许流量转发
iptables
iptables是与linux内核集成的包过滤防火墙系统,几乎所有的linux发行版本都会包含iptables的功能
- 表(table)
- 链(chain)
- 规则(rule)
- ACCEPT, REJECT, DROP
filter表中包含的链:
- INPUT
- FORWARD
- OUTPUT
1 | # 查看filter表 |
Docker容器的数据管理简介
Docker容器的数据卷
Docker的数据卷容器
Docker数据卷的备份与还原
Docker容器跨主机网络访问
使用网桥实现跨主机容器连接
使用Open vSwitch实现跨主机容器连接
使用weave实现跨主机容器连接
Docker Compose
前面我们使用 Docker 的时候,定义 Dockerfile 文件,然后使用 docker build、docker run 等命令操作容器。然而微服务架构的应用系统一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,那么效率之低,维护量之大可想而知
使用 Docker Compose 可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具
docker-compose安装
1 | pip3 install docker-compose -U |
查看安装是否成功
1 | docker-compose version |
docker-compose命令
Docker compose的使用非常类似于docker命令的使用,但是需要注意的是大部分的compose命令都需要到docker-compose.yml文件所在的目录下才能执行
- compose以守护进程模式运行加-d选项
1 | docker-compose up -d |
- compose自定义命名运行
1 | # eg. |
- 查看有哪些服务,使用docker-compose ps命令,非常类似于docker的ps命令
1 | docker-compose ps |
- 查看compose日志
1 | docker-compose logs web |
- 停止compose服务
1 | docker-compose stop |
看到服务的状态为Exit退出状态
- 重启compose服务
1 | docker-compose restart |
- kill compose服务
1 | docker-compose kill |
- 删除compose服务
1 | docker-compose rm |
- 停止和删除容器、网络、卷、镜像
1 | docker-compose down [options] |
选项包括:
1 | –rmi type,删除镜像,类型必须是:all,删除compose文件中定义的所有镜像;local,删除镜像名为空的镜像 |
- 构建(重新构建)项目中的服务容器
1 | docker-compose build [options] [--build-arg key=val...] [SERVICE...] |
选项包括:
1 | –compress 通过gzip压缩构建上下环境 |
网桥
使用docker-compose会创建一个stack,可以管理多个容器,一个stack下的多个容器互相之间是通过默认网桥连接的,但是如果想跟其他stack中容器连接需要自己新建网桥
1 | docker network create name |
如图中两个网桥连接下面各个容器