Docker学习
Docker安装
安装Docker
查看Linux版本内核
1 | 1.查看Linux版本内核 |
安装
1 | 1.卸载旧的版本 |
卸载Docker
1 | 1.卸载依赖 |
阿里云镜像加速
1 | 配置镜像加速器 |
设置开机自动启动
1 | systemctl enable docker |
查看docker服务状态
1 | systemctl status docker |
查看docker具体信息
1 | docker info |
镜像使用
列出镜像
1 | docker images |
- REPOSITORY**:表示镜像的仓库源
- TAG**:**镜像的标签
- IMAGE ID**:**镜像ID
- CREATED**:**镜像创建时间
- SIZE**:**镜像大小
- 只显示镜像ID
1
docker images –q
- 直接列出镜像结果,并且只包含镜像ID和仓库名
1
docker images --format "{{.ID}}: {{.Repository}}"
获取镜像
1 | docker pull REPOSITORY:TAG |
查找镜像
1 | docker search REPOSITORY:TAG |
- NAME: 镜像仓库源的名称
- DESCRIPTION: 镜像的描述
- OFFICIAL: 是否 docker 官方发布
- stars: 类似 Github 里面的 star,表示点赞、喜欢的意思。
- AUTOMATED: 自动构建。
删除镜像
1 | docker rmi REPOSITORY |
使用某镜像来启动一个容器
1 | docker run -t -i REPOSITORY:TAG /bin/bash |
-i: 交互式操作
-t: 终端。
/bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。
如果你不指定一个镜像的版本标签,docker 将默认使用 REPOSITORY:latest 镜像。
更新镜像(更新前需要创建一个容器)
在运行的容器内使用 apt-get update 命令进行更新。
在完成操作之后,输入 exit 命令来退出这个容器。
通过commit命令来提交(将容器保存为镜像)
1
docker commit -m="描述信息" -a="镜像作者" 容器名/容器ID REPOSITORY:创建的目标镜像名
设置镜像标签
1 | docker tag 镜像ID REPOSITORY:新的标签名 |
镜像的导入导出
如果因为网络原因可以通过硬盘的方式传输镜像,虽然不规范,但是有效,但是这种方式导出的镜像名称和版本都是null,需要手动修改
将本地的镜像导出
1
docker save -o 导出的路径 镜像id
加载本地的镜像文件
1
docker load -i 镜像文件
虚悬镜像
镜像既没有仓库名,也没有标签,均为
1 | docker images -f dangling=true |
可以用下面的命令删除
1 | docker rmi $(docker images -q -f dangling=true) |
利用 commit 理解镜像构成
现在以定制一个 Web 服务器为例子,来讲解镜像是如何构建的。
1 | docker run --name webserver -d -p 80:80 nginx |
这条命令会用 nginx 镜像启动一个容器,命名为 webserver,并且映射了 80 端口,这样可以用浏览器去访问这个 nginx 服务器。直接用浏览器访问的话,会看到默认的 Nginx 欢迎页面。
现在,假设非常不喜欢这个欢迎页面,希望改成欢迎 Docker 的文字,可以使用 docker exec命令进入容器,修改其内容。
1 | docker exec -it webserver bash |
上传本地镜像到共有仓库
在 https://hub.docker.com 免费注册一个 Docker 账号。
登录
启动 docker 服务
1
systemctl start docker
1
2docker login
docker logout(退出)查看本地镜像
通过docker push 命令将自己的镜像推送到Docker HuB
使用tag标记镜像需要上传到的仓库
1
docker tag hello-world:latest username/hello-world:latest
使用push 上传镜像
1
docker push username/hello-world:latest
登陆hub.docker.com 查看上传结果
私有仓库的搭建并上传镜像至私有仓库
查找并下载 私有仓库的镜像 registry
1
2docker search registry
docker pull registry创建镜像的容器
1
docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --name myregistry registry:2
Registry服务默认会将上传的镜像保存在容器的/var/lib/registry,我们将主机的/opt/registry目录挂载到该目录,即可实现将镜像保存到主机的/opt/registry目录了。
上传镜像至私有仓库
要通过docker tag将该镜像标志为要推送到私有仓库:
1
docker tag nginx:latest localhost:5000/nginx:latest
通过 docker push 命令将 nginx 镜像 push到私有仓库中:
1
docker push localhost:5000/nginx:latest
访问 http://127.0.0.1:5000/v2/_catalog 查看私有仓库目录,可以看到刚上传的镜像了:
可能出现的异常
received unexpected HTTP status:500 Internal Server Error
解决方案:设置防火墙的权限
1 | setenforce 0 |
运行容器
运行容器需要定制具体镜像,如果镜像不存在,会直接下载
命令
1
docker run -d -p 宿主机端口:容器端口 --name 容器名称 镜像的标识|镜像名称[:tag]
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 ip 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被终止
常用的参数
-d:代表后台运行容器
-it 使用交互方式运行,进入容器查看内容
-p 宿主机端口:容器端口:为了映射当前Linux的端口和容器的端口
—name 容器名称:指定容器的名称
我们也可以使用 -p 标识来指定容器端口绑定到主机端口。
两种方式的区别是:
-P :是容器内部端口随机映射到主机的高端口。
-p : 是容器内部端口绑定到指定的主机端口。
查看正在运行的容器
查看全部正在运行的容器信息
1 | docker ps [-qa] |
-a 查看全部的容器,包括没有运行
-q 只查看容器的标识
查看容器日志
查看容器日志,以查看容器运行的信息
1 | docker logs -f 容器id |
- -f:可以滚动查看日志的最后几行
- —tail number 要显示日志条数
进入容器的内部
可以进入容器的内部进行操作
1 | docker exec -it 容器id /bin/bash |
复制内容到容器
将宿主机的文件复制到容器内部的指定目录
1 | docker cp 文件名称 容器id:容器内部路径 |
重启&启动&停止&删除容器&退出容器
重新启动容器
1 | docker restart 容器id |
启动停止运行的容器
1 | docker start/run 容器id |
- -d 后台运行
- -i 交换式运行
- —name 添加名字
- -t 添加标签
- -v 添加数据卷
- -rm 容器删除后清除缓存
停止指定的容器(删除容器前,需要先停止容器)
1 | docker stop 容器id |
停止全部容器
1 | docker stop $(docker ps -qa) |
删除指定容器
1 | docker rm 容器id |
强制停止当前容器
1 | docker kill 容器id |
退出容器
1 | exit #直接容器停止并退出 |
删除全部容器
1 | docker rm $(docker ps -qa) |
导出容器
1 | #docker export 容器ID > 存储路径 |
导入容器
1 | #docker import 容器快照/指定URL/某个目录 |
*注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
将website1发到nginx的容器中去
1 | docker volume create 卷名 |
Docker图形界面管理
DockerUI
DockerUI是一个基于Docker API提供图形化页面简单的容器管理系统,支持容器管理、镜像管理。
1 | docker run \ |
Shipyard
Shipyard也是基于Docker API实现的容器图形管理系统,支持container、images、engine、cluster等功能,可满足我 们基本的容器部署需求。 Shipyard分为手动部署和自动部署。
官方部署文档:https://www.shipyard-project.com/docs/deploy/
可视化
http://www.yunweipai.com/34991.html
Docker镜像加载原理
UnionFS(联合文件系统)
我们下载的时候看到的一层层就是这个!
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统得修改,作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统对包含所有底层的文件和目录。
Docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层次的文件系统UnionFS。
boots(boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会加载bootfs。
rootfs(root file system),在bootfs之上。包含的就是典型Linux系统中的/dev/,/proc,/bin,/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。
分层理解
所有的 Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于 Ubuntu Linux16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python’包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安补丁,就会创建第三个镜像层该镜像当前已经包含3个镜像层,如下图所示(这只是一个用于演示的很简单的例子)。
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了一个简单的例子,每个镜像层包含3个文件,而镜像包含了来自两个镜像层的6个文件。
上图中的镜像层跟之前图中的略有区别,主要目的是便于展示文件。下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个更新版本
这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为—个新镜像层添加到镜像当中。Docker通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统-的文件系统。
Linux上可用的存储引擎有AUFS、 Overlay2、 Device Mapper、Bts以及zFS。顾名思义,每种存储引擎都基于 Linux中对应的又仵系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。
Docker在 Windows上仅支持 windowsfilter—种存储引擎,该引擎基于NTFS文件系统之上实现了分层和CoW[1]。下图展示了与系统显示相同的三层镜像。所有镜像层堆并合并,对外提供统-的视图.
特点:
Docker镜像都是只读的,当容器启动时,—个新的可写层被加载到镜像的顶部这一层就是我们通常说的容器层,容器之下的都叫镜像层!
数据卷
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响镜像
- 卷会一直存在,直到没有容器使用
*数据卷的使用,类似于 Linux 下对目录或文件进行 mount。
创建数据卷
创建数据卷后,默认会存放在一个目录下/var/lib/docker/volumes/数据卷名称/_data
1 | docker volume create 数据卷名称 |
查看全部数据卷
查看全部数据卷信息
1 | docker volume ls |
查看数据卷详情
查看数据卷的详细信息,可以查询到存放的路径,创建时间等等
1 | docker volume inspect 数据卷名称 |
删除数据卷
删除指定的数据卷
1 | docker volume rm 数据卷名称 |
Docker数据卷容器
如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。
数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。
首先,创建一个命名的数据卷容器 dbdata:
1 | sudo docker run -d -v /数据卷容器名称 --name 数据卷容器名称 镜像的标识|镜像名称[:tag] |
然后,在其他容器中使用 —volumes-from 来挂载 dbdata 容器中的数据卷。
1 | sudo docker run -d --volumes-from 数据卷容器名称 --name db1 |
还可以使用多个 —volumes-from 参数来从多个容器挂载多个数据卷。
也可以从其他已经挂载了数据卷的容器来挂载数据卷。
1 | sudo docker run -d --name db3 --volumes-from db1 |
*注意:使用 —volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态。
如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。
容器映射数据卷
通过数据卷名称映射,如果数据卷不存在。Docker会帮你自动创建,会将容器内部自带的文件,存储在默认的存放路径中。
1 | docker run -d -p 8080:8080 --name tomcat -v 数据卷名称:容器内部的路径 镜像id |
通过路径映射数据卷,直接指定一个路径作为数据卷的存放位置。但是这个路径下是空的。
1 | docker run -d -p 8080:8080 --name tomcat -v 路径(/root/自己创建的文件夹):容器内部的路径 镜像id |
映射所有接口地址
使用 hostPort:containerPort 格式本地的 5000 端口映射到容器的 5000 端口,可以执行
1 | sudo docker run -d -p 5000:5000 镜像的标识|镜像名称 |
此时默认会绑定本地所有接口上的所有地址。
映射到指定地址的指定端口
可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1
1 | sudo docker run -d -p 127.0.0.1:5000:5000 镜像的标识|镜像名称 |
映射到指定地址的任意端口
使用 ip::containerPort 绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。
1 | sudo docker run -d -p 127.0.0.1::5000 镜像的标识|镜像名称 |
还可以使用 udp 标记来指定 udp 端口
1 | sudo docker run -d -p 127.0.0.1:5000:5000/udp 镜像的标识|镜像名称 |
查看映射端口配置
使用 docker port 来查看当前映射的端口配置,也可以查看到绑定的地址
1 | docker port nostalgic_morse 5000 |
注意:
容器有自己的内部网络和 ip 地址(使用 docker inspect 可以获取所有的变量,Docker 还可以有一个可变的网络配置。)
-p 标记可以多次使用来绑定多个端口
例如
1 | sudo docker run -d -p 5000:5000 -p 3000:80 镜像的标识|镜像名称 |
具名和匿名挂载
匿名挂载
1
docker run -d -P --name 容器名 -v 容器内路径 镜像名
具名挂载
1
docker run -d -P --name 容器名 -v 卷名:容器内路径 镜像名
所有的docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes/xxx/_data
1
2
3
4如何确定是具名挂载还是匿名挂载,还是指定路径挂载
-v 容器内路径 #匿名挂载
-v 卷名:容器内路径 #具名挂载
-v /宿主机路径::容器内路径 #指定路径挂载1
2
3
4通过 -v 容器内路径,ro rw 改变读写权限
ro readonly #只读
rw readwrite #可读可写
docker run -d -p --name nginx2 -v mynginx:/etc/nginx:ro nginx
将数据从宿主机挂载到容器中的三种方式
Docker提供三种方式将数据从宿主机挂载到容器中:
• volumes:Docker管理宿主机文件系统的一部分(/var/lib/docker/volumes)。保存数据的最佳方式。
• bind mounts-:将宿主机上的任意位置的文件或者目录挂载到容器中。
• tmpfs:挂载存储在主机系统的内存中,而不会写入主机的文件系统。如果不希望将数据持久存储在任何位置,可以使用 tmpfs,同时避免写入容器可写层提高性能。
Volume特点:
• 多个运行容器之间共享数据,多个容器可以同时挂载相同的卷。
• 当容器停止或被移除时,该卷依然存在。
• 当明确删除卷时,卷才会被删除。
• 将容器的数据存储在远程主机或其他存储上(间接)
• 将数据从一台Docker主机迁移到另一台时,先停止容器,然后备份卷的目录(/var/lib/docker/volumes/)
Bind Mounts特点:
• 从主机共享配置文件到容器。默认情况下,挂载主机/etc/resolv.conf到每个容器,提供DNS解析。
• 在Docker主机上的开发环境和容器之间共享源代码。例如,可以将Maven target目录挂载到容器中,每次在Docker主机 上构建Maven项目时,容器都可以访问构建的项目包。
• 当Docker主机的文件或目录结构保证与容器所需的绑定挂载一致时
Dockerfile自定义镜像
实战一
1 | 创建一个dockerfile文件,名字可以随机 建议Dockerfile |
1 | FROM centos |
实战二(Tomcat)
准备镜像文件tomcat压缩包,jdk的压缩包
编写dockerfile文件,官方命名Dockerfile,build会自动寻找这个文件,就不需要-f 制定了!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15FROM centos
MAINTAINER zzz<2251@qq.com>
COPY readme.txt /usr/Local/readme.txt
ADD jdk-8u11-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.22.tar.gz /usr/Local/
RUN yum-y install vim
ENV MYPATH /usr/Local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/Local/idk.8.0_11
ENV CLASSPATH $JAVA_HOME/Lib/dt.jar:$JAVA_HOME/Lib/tools.jar
ENV CATALINA_HOME /usr/Local/apache-tomcat-9.0.22
ENV CATALINA_BASH /usr/Local/apache-tomcat-9.0.22
ENV PATH $PATH: $JAVA_HOME/bin:$CATALINA_HOME/Lib:$CATALINA_HOME/bin
EXPOSE 8080
CMD /usr/Local/apache-tomcat-90.22/bin/startup.sh && tail -F /usr/Local/apache-tomcat-9022/bin/logs/catalina.out构建镜像
1
2
3
4
5docker build -t diytomcat .
. 是上下文路径
上下文路径,是指 docker 在构建镜像,有时候想要使用到本机的文件(比如复制),docker build 命令得知这个路径后,会将路径下的所有内容打包。
由于 docker 的运行模式是 C/S。我们本机是 C,docker 引擎是 S。实际的构建过程是在 docker 引擎下完成的,所以这个时候无法用到我们本机的文件。这就需要把我们本机的指定目录下的文件一起打包提供给 docker 引擎使用。
如果未说明最后一个参数,那么默认上下文路径就是 Dockerfile 所在的位置。启动镜像
1
2
3docker run -d 9090:8080 --name zzzcomcat -v 本地目录路径:容器内部目录路径 本地目录路径:容器内部目录路径(日志挂载)镜像名
-d 后台运行
-p 指定端口访问测试
发布项目
出现资源拒绝访问,解决办法,增加一个tag
构建PHP网站环境镜像
1 | FROM centos:6 |
构建JAVA网站环境镜像
1 | FROM centos:6 |
构建支持SSH服务的镜像
1 | FROM centos:6 |
Docker网络
当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。它会在挂载到它的网口之间进行转发。
同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口。比如典型的 172.17.42.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。
当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。
接下来的部分将介绍在一些场景中,Docker 所有的网络定制配置。以及通过 Linux 命令来调整、补充、甚至替换 Docker 默认的网络配置。
快速配置指南
下面是一个跟 Docker 网络相关的命令列表。
其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效。
- -b BRIDGE or —bridge=BRIDGE —指定容器挂载的网桥
- —bip=CIDR —定制 docker0 的掩码
- -H SOCKET… or —host=SOCKET… —Docker 服务端接收命令的通道
- —icc=true|false —是否支持容器之间进行通信
- —ip-forward=true|false —请看下文容器之间的通信
- —iptables=true|false —禁止 Docker 添加 iptables 规则
- —mtu=BYTES —容器网络中的 MTU
下面2个命令选项既可以在启动服务时指定,也可以 Docker 容器启动(docker run)时候指定。在 Docker 服务启动的时候指定则会成为默认值,后面执行 docker run 时可以覆盖设置的默认值。
- —dns=IP_ADDRESS… —使用指定的DNS服务器
- —dns-search=DOMAIN… —指定DNS搜索域
最后这些选项只有在 docker run 执行时使用,因为它是针对容器的特性内容。
- -h HOSTNAME or —hostname=HOSTNAME —配置容器主机名
- —link=CONTAINER_NAME:ALIAS —添加到另一个容器的连接
- —net=bridge|none|container:NAME_or_ID|host —配置容器的桥接模式
- -p SPEC or —publish=SPEC —映射容器端口到宿主主机
- -P or —publish-all=true|false —映射容器所有端口到宿主主机
Docker配置DNS
Docker 没有为每个容器专门定制镜像,那么怎么自定义配置容器的主机名和 DNS 配置呢?
秘诀就是它利用虚拟文件来挂载到来容器的 3 个相关配置文件。
在容器中使用 mount 命令可以看到挂载信息:
1 | mount |
这种机制可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 dns 配置通过 /etc/resolv.conf 文件立刻得到更新。
如果用户想要手动指定容器的配置,可以利用下面的选项。
-h HOSTNAME or —hostname=HOSTNAME
设定容器的主机名,它会被写到容器内的 /etc/hostname 和 /etc/hosts。但它在容器外部看不到,既不会在 docker ps 中显示,也不会在其他的容器的 /etc/hosts 看到。
—link=CONTAINER_NAME:ALIAS
选项会在创建容器的时候,添加一个其他容器的主机名到 /etc/hosts 文件中,让新容器的进程可以使用主机名 ALIAS 就可以连接它。
—dns=IP_ADDRESS
添加 DNS 服务器到容器的 /etc/resolv.conf 中,让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名。
—dns-search=DOMAIN
设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,DNS 不仅搜索host,还会搜索 host.example.com。
注意:如果没有上述最后 2 个选项,Docker 会默认用主机上的 /etc/resolv.conf 来配置容器。
Docker容器访问控制
容器的访问控制,主要通过 Linux 上的 iptables 防火墙来进行管理和实现。iptables 是 Linux 上默认的防火墙软件,在大部分发行版中都自带。
容器访问外部网络
容器要想访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。
1 | sysctl net.ipv4.ip_forward |
如果为 0,说明没有开启转发,则需要手动打开。
1 | sysctl -w net.ipv4.ip_forward=1 |
如果在启动 Docker 服务的时候设定 —ip-forward=true, Docker 就会自动设定系统的 ip_forward 参数为 1。
容器之间访问
容器之间相互访问,需要两方面的支持。
容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到 docker0 网桥上。
本地系统的防火墙软件 — iptables 是否允许通过。
访问所有端口
当启动 Docker 服务时候,默认会添加一条转发策略到 iptables 的 FORWARD 链上。策略为通过(ACCEPT)还是禁止(DROP)取决于配置—icc=true(缺省值)还是 —icc=false。当然,如果手动指定 —iptables=false 则不会添加 iptables 规则。
可见,默认情况下,不同容器之间是允许网络互通的。如果为了安全考虑,可以在 /etc/default/docker 文件中配置 DOCKER_OPTS=—icc=false 来禁止它。
访问指定端口
在通过 -icc=false 关闭网络访问后,还可以通过 —link=CONTAINER_NAME:ALIAS 选项来访问容器的开放端口。
例如,在启动 Docker 服务时,可以同时使用 icc=false —iptables=true 参数来关闭允许相互的网络访问,并让 Docker 可以修改系统中的 iptables 规则。
此时,系统中的 iptables 规则可能是类似
1 | sudo iptables -nL |
之后,启动容器(docker run)时使用 —link=CONTAINER_NAME:ALIAS 选项。Docker 会在 iptable 中为 两个容器分别添加一条 ACCEPT 规则,允许相互访问开放的端口(取决于 Dockerfile 中的 EXPOSE 行)。
当添加了 —link=CONTAINER_NAME:ALIAS 选项后,添加了 iptables 规则。
1 | sudo iptables -nL |
注意:—link=CONTAINER_NAME:ALIAS 中的 CONTAINER_NAME 目前必须是 Docker 分配的名字,或使用 —name 参数指定的名字。主机名则不会被识别。
Docker端口映射实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
容器访问外部实现
容器所有到外部网络的连接,源地址都会被NAT成本地系统的IP地址。这是使用 iptables 的源地址伪装操作实现的。
查看主机的 NAT 规则。
1 | sudo iptables -t nat -nL |
其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。
外部访问容器实现
容器允许外部访问,可以在 docker run 时候通过 -p 或 -P 参数来启用。
不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则。
使用 -P 时:
1 | iptables -t nat -nL |
使用 -p 80:80 时:
1 | iptables -t nat -nL |
注意:
- 这里的规则映射了 0.0.0.0,意味着将接受主机来自所有接口的流量。用户可以通过 -p IP:host_port:container_port 或 -p IP::port 来指定允许访问容器的主机上的 IP、接口等,以制定更严格的规则。
- 如果希望永久绑定到某个固定的 IP 地址,可以在 Docker 配置文件 /etc/default/docker 中指定 DOCKER_OPTS=”—ip=IP_ADDRESS”,之后重启 Docker 服务即可生效。
配置docker0网桥
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。
Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值。这些值都可以在服务启动的时候进行配置。
- —bip=CIDR — IP 地址加掩码格式,例如 192.168.1.5/24
- —mtu=BYTES — 覆盖默认的 Docker mtu 配置
也可以在配置文件中配置 DOCKER_OPTS,然后重启服务。
由于目前 Docker 网桥是 Linux 网桥,用户可以使用 brctl show 来查看网桥和端口连接信息。
1 | sudo brctl show |
*注:brctl 命令在 Debian、Ubuntu 中可以使用 sudo apt-get install bridge-utils 来安装。
每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口。使用本地主机上 docker0 接口的 IP 作为所有容器的默认网关。
1 | sudo docker run -i -t --rm base /bin/bash |
Docker自定义网桥
除了默认的 docker0 网桥,用户也可以指定网桥来连接各个容器。
在启动 Docker 服务的时候,使用 -b BRIDGE或—bridge=BRIDGE 来指定使用的网桥。
如果服务已经运行,那需要先停止服务,并删除旧的网桥。
1 | sudo service docker stop |
然后创建一个网桥 bridge0。
1 | sudo brctl addbr bridge0 |
查看确认网桥创建并启动。
1 | ip addr show bridge0 |
配置 Docker 服务,默认桥接到创建的网桥上。
1 | echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker |
启动 Docker 服务。
新建一个容器,可以看到它已经桥接到了 bridge0 上。
可以继续用 brctl show 命令查看桥接的信息。另外,在容器中可以使用 ip addr 和 ip route 命令来查看 IP 地址配置和路由信息。
Docker 网络模式
Docker支持五种网络模式
bridge
默认网络,Docker启动后创建一个docker0网桥,默认创建的容器也是添加到这个网桥中;IP地址段是172.17.0.1/16
host
容器不会获得一个独立的network namespace,而是与宿主机共用一个。
none
获取独立的network namespace,但不为容器进行任何网络配置。
container
与指定的容器使用同一个network namespace,网卡配置也都是相同的。
自定义
自定义网桥,默认与bridge网络一样。
—link
通过容器名来实现互ping
1 | docker exec -it 容器名2 --link 容器名1 容器名 |
反过来,容器1 ping不通容器2。 其实这个容器2就是在本地配置了容器1的配置。
1 | 查看hosts配置, |
本质探究:—link就是在hosts配置中增加了一个地址
自定义网络,不适用docker0
docker问题:不支持容器名连接访问
自定义网络
查看所有的网络
1 | docker network ls |
网络模式
bridge:桥接docker(默认)
none:不配置网络
host:和宿主机共享网络
container:容器网络连通(用的少,局限很大)
自定义一个网络
1 | docker network create --driver bridge --subnet 子网 --gateway 网关 网络名 |
实现tomcat-01 连接tomcat-net-01
1 | docker network connect 网络名 容器名 |
Docker安全
评估 Docker 的安全性时,主要考虑三个方面:
- 由内核的名字空间和控制组机制提供的容器内在安全
- Docker程序(特别是服务端)本身的抗攻击性
- 内核安全性的加强机制对容器安全性的影响
内核名字空间
Docker 容器和 LXC 容器很相似,所提供的安全特性也差不多。当用 docker run 启动一个容器时,在后台 Docker 为容器创建了一个独立的名字空间和控制组集合。
名字空间提供了最基础也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其它容器发现和作用。
每个容器都有自己独有的网络栈,意味着它们不能访问其他容器的 sockets 或接口。不过,如果主机系统上做了相应的设置,容器可以像跟主机交互一样的和其他容器交互。当指定公共端口或使用 links 来连接 2 个容器时,容器就可以相互通信了(可以根据配置来限制通信的策略)。
从网络架构的角度来看,所有的容器通过本地主机的网桥接口相互通信,就像物理机器通过物理交换机通信一样。
那么,内核中实现名字空间和私有网络的代码是否足够成熟?
内核名字空间从 2.6.15 版本(2008 年 7 月发布)之后被引入,数年间,这些机制的可靠性在诸多大型生产系统中被实践验证。
实际上,名字空间的想法和设计提出的时间要更早,最初是为了在内核中引入一种机制来实现 OpenVZ 的特性。
而 OpenVZ 项目早在 2005 年就发布了,其设计和实现都已经十分成熟。
控制组
控制组是 Linux 容器机制的另外一个关键组件,负责实现资源的审计和限制。
它提供了很多有用的特性;以及确保各个容器可以公平地分享主机的内存、CPU、磁盘 IO 等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。
尽管控制组不负责隔离容器之间相互访问、处理数据和进程,它在防止拒绝服务(DDOS)攻击方面是必不可少的。尤其是在多用户的平台(比如公有或私有的 PaaS)上,控制组十分重要。例如,当某些应用程序表现异常的时候,可以保证一致地正常运行和性能。
控制组机制始于 2006 年,内核从 2.6.24 版本开始被引入。
Docker服务端的防护
运行一个容器或应用程序的核心是通过 Docker 服务端。Docker 服务的运行目前需要 root 权限,因此其安全性十分关键。
首先,确保只有可信的用户才可以访问 Docker 服务。Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,这就容易让容器突破资源限制。例如,恶意用户启动容器的时候将主机的根目录/映射到容器的 /host 目录中,那么容器理论上就可以对主机的文件系统进行任意修改了。这听起来很疯狂?但是事实上几乎所有虚拟化系统都允许类似的资源共享,而没法禁止用户共享主机根文件系统到虚拟机系统。
这将会造成很严重的安全后果。因此,当提供容器创建服务时(例如通过一个 web 服务器),要更加注意进行参数的安全检查,防止恶意的用户用特定参数来创建一些破坏性的容器
为了加强对服务端的保护,Docker 的 REST API(客户端用来跟服务端通信)在 0.5.2 之后使用本地的 Unix 套接字机制替代了原先绑定在 127.0.0.1 上的 TCP 套接字,因为后者容易遭受跨站脚本攻击。现在用户使用 Unix 权限检查来加强套接字的访问安全。
用户仍可以利用 HTTP 提供 REST API 访问。建议使用安全机制,确保只有可信的网络或 VPN,或证书保护机制(例如受保护的 stunnel 和 ssl 认证)下的访问可以进行。此外,还可以使用 HTTPS 和证书来加强保护。
最近改进的 Linux 名字空间机制将可以实现使用非 root 用户来运行全功能的容器。这将从根本上解决了容器和主机之间共享文件系统而引起的安全问题。
终极目标是改进 2 个重要的安全特性:
- 将容器的 root 用户映射到本地主机上的非 root 用户,减轻容器和主机之间因权限提升而引起的安全问题;
- 允许 Docker 服务端在非 root 权限下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。这些子进程将只允许在限定范围内进行操作,例如仅仅负责虚拟网络设定或文件系统管理、配置操作等。
最后,建议采用专用的服务器来运行 Docker 和相关的管理服务(例如管理服务比如 ssh 监控和进程监控、管理工具 nrpe、collectd 等)。其它的业务服务都放到容器中去运行。
内核能力机制
能力机制(Capability)是 Linux 内核一个强大的特性,可以提供细粒度的权限访问控制。
Linux 内核自 2.2 版本起就支持能力机制,它将权限划分为更加细粒度的操作能力,既可以作用在进程上,也可以作用在文件上。
例如,一个 Web 服务进程只需要绑定一个低于 1024 的端口的权限,并不需要 root 权限。那么它只需要被授权 net_bind_service 能力即可。此外,还有很多其他的类似能力来避免进程获取 root 权限。
默认情况下,Docker 启动的容器被严格限制只允许使用内核的一部分能力。
使用能力机制对加强 Docker 容器的安全有很多好处。通常,在服务器上会运行一堆需要特权权限的进程,包括有 ssh、cron、syslogd、硬件管理工具模块(例如负载模块)、网络配置工具等等。容器跟这些进程是不同的,因为几乎所有的特权进程都由容器以外的支持系统来进行管理。
- ssh 访问被主机上ssh服务来管理;
- cron 通常应该作为用户进程执行,权限交给使用它服务的应用来处理;
- 日志系统可由 Docker 或第三方服务管理;
- 硬件管理无关紧要,容器中也就无需执行 udevd 以及类似服务;
- 网络管理也都在主机上设置,除非特殊需求,容器不需要对网络进行配置。
从上面的例子可以看出,大部分情况下,容器并不需要“真正的” root 权限,容器只需要少数的能力即可。为了加强安全,容器可以禁用一些没必要的权限。
- 完全禁止任何 mount 操作;
- 禁止直接访问本地主机的套接字;
- 禁止访问一些文件系统的操作,比如创建新的设备、修改文件属性等;
- 禁止模块加载。
这样,就算攻击者在容器中取得了 root 权限,也不能获得本地主机的较高权限,能进行的破坏也有限。
默认情况下,Docker采用 白名单 机制,禁用 必需功能 之外的其它权限。
当然,用户也可以根据自身需求来为 Docker 容器启用额外的权限。
其它安全特性
除了能力机制之外,还可以利用一些现有的安全机制来增强使用 Docker 的安全性,例如 TOMOYO, AppArmor, SELinux, GRSEC 等。
Docker 当前默认只启用了能力机制。用户可以采用多种方案来加强 Docker 主机的安全,例如:
- 在内核中启用 GRSEC 和 PAX,这将增加很多编译和运行时的安全检查;通过地址随机化避免恶意探测等。并且,启用该特性不需要 Docker 进行任何配置。
- 使用一些有增强安全特性的容器模板,比如带 AppArmor 的模板和 Redhat 带 SELinux 策略的模板。这些模板提供了额外的安全特性。
- 用户可以自定义访问控制机制来定制安全策略。
跟其它添加到 Docker 容器的第三方工具一样(比如网络拓扑和文件系统共享),有很多类似的机制,在不改变 Docker 内核情况下就可以加固现有的容器。
总体来看,Docker 容器还是十分安全的,特别是在容器内不使用 root 权限来运行进程的话。
另外,用户可以使用现有工具,比如 Apparmor, SELinux, GRSEC 来增强安全性;甚至自己在内核中实现更复杂的安全机制。
Prometheus监控Docker主机
Prometheus 概述
Prometheus(普罗米修斯)是一个最初在SoundCloud上构建的监控系统。自2012年成为社区 开源项目,拥有非常活跃的开发人员和用户社区。为强调开源及独立维护,Prometheus于2016 年加入云原生云计算基金会(CNCF),成为继Kubernetes之后的第二个托管项目。
Prometheus 特点:
• 多维数据模型:由度量名称和键值对标识的时间序列数据
• PromQL:一种灵活的查询语言,可以利用多维数据完成复杂的查询
• 不依赖分布式存储,单个服务器节点可直接工作
• 基于HTTP的pull方式采集时间序列数据
• 推送时间序列数据通过PushGateway组件支持
• 通过服务发现或静态配置发现目标
• 多种图形模式及仪表盘支持(grafana)
•Prometheus Server:收集指标和存储时间序列数据,并提供查询接口
• ClientLibrary:客户端库
• Push Gateway:短期存储指标数据。主要用于临时性的任务
• Exporters:采集已有的第三方服务监控指标并暴露metrics
• Alertmanager:告警
• Web UI:简单的Web控制台
Prometheus 部署
1 | Docker部署:https://prometheus.io/docs/prometheus/latest/installation/ |
构建容器监控系统
cAdvisor+InfluxDB+Grafana
cAdvisor:Google开源的工具,用于监控Docker主机和容器系统资源,通过图形页面实时显示数据,但不存储;它通过 宿主机/proc、/sys、/var/lib/docker等目录下文件获取宿主机和容器运行信息。 InfluxDB:是一个分布式的时间序列数据库,用来存储cAdvisor收集的系统资源数据。 Grafana:可视化展示平台,可做仪表盘,并图表页面操作很方面,数据源支持zabbix、Graphite、InfluxDB、 OpenTSDB、Elasticsearch等
它们之间关系: cAdvisor容器数据采集->InfluxDB容器数据存储->Grafana可视化展示
1 | 部署 |
企业级镜像仓库Harbor
Harbor概述
Habor是由VMWare公司开源的容器镜像仓库。事实上,Habor是在Docker Registry上进行了相应的 企业级扩展,从而获得了更加广泛的应用,这些新的企业级特性包括:管理用户界面,基于角色的访 问控制 ,AD/LDAP集成以及审计日志等,足以满足基本企业需求。 官方地址:https://vmware.github.io/harbor/cn/
Harbor部署
Harbor安装有3种方式:
• 在线安装:从Docker Hub下载Harbor相关镜像,因此安装软件包非常小
• 离线安装:安装包包含部署的相关镜像,因此安装包比较大
• OVA安装程序:当用户具有vCenter环境时,使用此安装程序,在部署OVA后启动Harbor
1 | # tar zxvf harbor-offline-installer-v1.6.1.tgz |
基本使用
1 | 1、配置http镜像仓库可信任 |
Docker-Compose
Docker-compose是用于定义和运行多容器 Docker 应用程序的工具。使用”Docker-compose”,您可以使用 YAML 文件来配置应用程序的服务。然后,通过单个命令,从配置创建和启动所有服务。
在所有环境中Docker-compose作品:生产、暂存、开发、测试以及 CI 工作流。
使用Docker-compose基本上是一个三步过程:
- 使用 定义应用的环境,以便可以在任何地方复制。
Dockerfile
- 定义在中管理应用的服务,以便它们可以在隔离环境中一起运行。
docker-compose.yml
- 运行和Docker-compose启动并运行整个应用。
docker-compose up
下载并安装Docker-Compose
1 | 1. 官方下载(下载很慢) |
体验
python应用:计数器。
1.应用app.py
2.Dockerfile应用打包为镜像
3.Docker-compose yaml文件(定义整个服务依赖的环境,web,redis,完整的上线服务)
4.启动compose项目
准备工作
1
2yum install python-pip #pip是python包管理工具
yum install epel-release #报错的话执行为项目创建目录
1
2mkdir composetest
cd composetest在项目目录中创建一个名为app.py文件,内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)在项目目录中创建一个名为requirements.txt文件,内容如下
1
2flask
redis在项目目录中创建一个名为Dockerfile文件
1
2
3
4
5
6
7
8
9
10
11
12FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
这告诉docker
从Python 3.6镜像开始构建镜像。
将当前目录添加. 到/code镜像中的路径中。
将工作目录设置为/code
安装Python依赖项
将容器的默认命令设置为python app.py.在项目目录中创建一个名为docker-compose.yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15version: "3.8"
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
redis:
image: "redis:alpine"
此Compose文件定义了俩个服务,web和redis。
使用从Dockerfile当前目录中构建的图像
将容器上的公共端口5000转发到主机上的端口5000。我们使用Flask Web服务器的默认端口5000
该redis服务使用从Docker Hub注册表中提取的公共 Redis镜像使用Compose构建和运行应用程序,在项目目录中,通过运行docker-compose up 启动应用程序
YAML文件格式及编写注意事项
YAML是一种标记语言很直观的数据序列化格式,可读性高。类似于XML数据描述语言,语法比XML简单的很多。 YAML数据结构通过缩进来表示,连续的项目通过减号来表示,键值对用冒号分隔,数组用中括号括起来,hash用花括号括起 来。
YAML文件格式注意事项:
- 不支持制表符tab键缩进,需要使用空格缩进
- 通常开头缩进2个空格
- 字符后缩进1个空格,如冒号、逗号、横杆
- 用井号注释
- 如果包含特殊字符用单引号引起来
- 布尔值(true、false、yes、no、on、off)必须用引号括起来,这样分析器会将他们解释为字符串。
Docker-Compose管理MySQL和Tomcat容器
yaml文件编辑
官方案例(https://docs.docker.com/compose/compose-file/#compose-file-structure-and-examples)
1 | yml文件以key:value方式来指定配置信息 |
使用docker-compose命令管理容器
在使用docker-compose的命令时,默认会在当前目录下找docker-compose.yml文件
基于docker-compose.yml启动管理的容器
1
docker-compose up -d
关闭并删除容器
1
docker-compose down
开启|关闭|重启容器
1
docker-compose start|stop|restart
查看由docker-compose管理的容器
1
docker-compose ps
查看日志
1
#docker-compose logs -f
docker-compose配合Dockerfile使用(统一在/opt目录下操作)
使用docker-compose.yml文件以及Dockerfile文件在生成自定义镜像的同时启动当前镜像,并且由docker-compose去管理容器
1 | 1.首先生成一个文件夹,在其内编写docker-compose.yml文件 |
docker-compose说明
管理控制容器或是镜像的工具
管理控制容器
1
2
3
4
5
6
7
8
9
10
11
12直接使用docker-compose.yml文件
文件的结构
version:版本
services: 服务内容(可以同时存在多个服务)
servicesname: 服务名称,下级放的是服务相关的属性和设置(如 myweb)
restart: 服务启动的时机
image: 创建容器时使用的镜像
container_name:创建后容器的名称
ports: 服务涉及到端口(不能和系统中的端口冲突)
- 宿主机端口:容器端口
volumes:指定容器挂载的数据卷
- 宿主机路径:容器内的路径管理控制镜像(需要Dockerfile文件的支撑)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
201 创建 Dockerfile文件
from:指定当前自定义镜像依赖的环境
copy:将相对路径下的内容复制到自定义镜像中
workdir:声明镜像的默认工作目录
run:执行的命令,可以编写多个
cmd:需要执行的命令(在workdir下执行的,cmd可以写多个,只以最后一个为准)
2 创建Docker-compose.yml文件去调用Dockerfile生成镜像
version:'3.1' 版本
services: 服务
ssm: 服务名称
restart: 启动时机
build: # 构建自定义镜像时使用
context:../# 指定dockerfile文件的所在路径(相对)
dockerfile:Dockerfile# 指定Dockerfile文件名称
image: ssm:1.0.1 #生成后自定义镜像的名称
container_name:ssm #容器名称
ports:
-8081:8080
environment: #系统环境 (语言,时区等的设置)
TZ:Asia/Shanghai
开源项目
搭建博客(https://docs.docker.com/compose/wordpress/)
Docker Compose项目
术语
首先介绍几个术语。
服务(service):一个应用容器,实际上可以运行多个相同镜像的实例。
项目(project):由一组关联的应用容器组成的一个完整业务单元。
可见,一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。
场景
下面,我们创建一个经典的 Web 项目:一个 Haproxy,挂载三个 Web 容器。
创建一个 compose-haproxy-web 目录,作为项目工作目录,并在其中分别创建两个子目录:haproxy 和 web。
Web 子目录
这里用 Python 程序来提供一个简单的 HTTP 服务,打印出访问者的 IP 和 实际的本地 IP。
index.py
编写一个 index.py 作为服务器文件,代码为
1 | !/usr/bin/python |
index.html
生成一个临时的 index.html 文件,其内容会被 index.py 更新。
1 | touch index.html |
Dockerfile
生成一个 Dockerfile,内容为
1 | FROM python:2.7 |
haproxy 目录
在其中生成一个 haproxy.cfg 文件,内容为
1 | global |
docker-compose.yml
编写 docker-compose.yml 文件,这个是 Compose 使用的主模板文件。内容十分简单,指定 3 个 web 容器,以及 1 个 haproxy 容器。
1 | weba: |
运行 compose 项目
现在 compose-haproxy-web 目录长成下面的样子。
1 | compose-haproxy-web |
在该目录下执行 docker-compose up 命令,会整合输出所有容器的输出。
1 | $sudo docker-compose up |
此时访问本地的 80 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。
访问本地 70 端口,可以查看到 haproxy 的统计信息。
当然,还可以使用 consul、etcd 等实现服务发现,这样就可以避免手动指定后端的 web 容器了,更为灵活。