docker
一、 Docker 概述
1.1 什么是Docker
-
使用最广泛的开源容器引擎
-
一种操作系统级的虚拟化技术
-
依赖于Linux内核特效:Namespace(资源隔离)和Cgroups(资源限制)
-
一个简单的应用程序打包工具
Docker处于操作系统和虚拟容器(lxc 和 libcontainer)之上。会通过调用cgroup , namespaces 和 libcontainer等系统层面的接口来完成资源的分配与相互隔离:
1.2 Docker设计目标
优点
-
简洁高效 共享公共库和程序 不需要安装操作系统
-
提供简单的应用程序打包工具(强大的可移植性)
-
开发人员和运维人员职责逻辑分离(更快的部署交付)
-
多环境保持一致性(高效的资源利用)
缺点
-
隔离性不强,共用linux内核,安全性先天缺陷
1.3 Docker功能组件☆
-
Docker Client: 客户端
-
Docker使用C/S的软件架构,Client端包含了容器管理所需要的命令行工具,通常会与服务端部署在同一台机器上。
-
在日常操作中,我们可以通过命令行的方式,与服务端进行交互。
-
除了客户端以外,Docker也提供了REST API的方式与服务端进行交互,这通常是在程序代码中使用。
-
-
Docker Daemon: 守护进程
-
接受Client传入的请求, 它将控制所有容器的运行并监听Docker client的命令,帮助Client完成请求命令,将Docker client要的执行结果再返回给client端
-
-
Docker Images:镜像
-
如果有使用过VMware虚拟机,会发现里面有一个很有用的功能:模板。在定制好模板后,我们就可以根据模板批量生成虚拟机。
-
镜像的获取有两种方式
-
一种是使用他人已经封装好的镜像,开箱即可使用
-
一种则是通过Dockerfile等方式自定义封装,这种方式可以满足我们对镜像的定制化要求
-
-
-
Docker Container: 容器
-
来运行和隔离应用。容器是由镜像创建而成,可被看作镜像的运行实例。单个镜像可支持创建多个容器。
-
Docker提供了一整套对于容器的操作命令,用户可以对容器进行创建、启动、停止等动作。
-
-
Docker Registry:镜像仓库
-
它可以被部署在企业内部做为私有仓库,也可以部署在互联网做为公共仓库开放使用。
-
只需要将镜像上传到仓库保存,即可在其他主机上同时下载镜像,这无疑会大大提升部署的效率
-
1.4 容器 VS 虚拟机
容器:共享宿主机的内核,进程隔离,打包了文件系统
虚机:有操作系统,系统级隔离
-
虚拟机(重量级)
-
基础设施(Infrastructure):它可以是你的个人电脑,数据中心的服务器,或者是云主机。
-
虚拟机管理系统(Hypervisor):利用Hypervisor,可以在主操作系统之上运行多个不同的从操作系统。类型1的Hypervisor有支持MacOS的HyperKit,支持Windows的Hyper-V、Xen以及KVM。类型2的Hypervisor有VirtualBox和VMWare workstation。
-
客户机操作系统(Guest Operating System):假设你需要运行3个相互隔离的应用,则需要使用Hypervisor启动3个客户机操作系统,也就是3个虚拟机。这些虚拟机都非常大,也许有700MB,这就意味着它们将占用2.1GB的磁盘空间。更糟糕的是,它们还会消耗很多CPU和内存。(系统级隔离)
-
各种依赖:每一个客户机操作系统都需要安装许多依赖。如果你的应用需要连接PostgreSQL的话,则需要安装libpq-dev;如果你使用Ruby的话,应该需要安装gems;如果使用其他编程语言,比如Python或者Node.js,都会需要安装对应的依赖库。
-
应用:安装依赖之后,就可以在各个客户机操作系统分别运行应用了,这样各个应用就是相互隔离的
-
-
容器(轻量级)
-
基础设施(Infrastructure):它可以是你的个人电脑,数据中心的服务器,或者是云主机。
-
主操作系统(Host Operating System):所有主流的Linux发行版都可以运行Docker。对于MacOS和Windows,也有一些办法”运行”Docker。
-
Docker守护进程(Docker Daemon):Docker守护进程取代了Hypervisor,它是运行在操作系统之上的后台进程,负责管理Docker容器。
-
各种依赖:对于Docker,应用的所有依赖都打包在Docker镜像中,Docker容器是基于Docker镜像创建的。
-
应用:应用的源代码与它的依赖都打包在Docker镜像中,不同的应用需要不同的Docker镜像。不同的应用运行在不同的Docker容器中,它们是相互隔离的。
-
-
容器和虚拟机的参数对比
| 项目 | 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级 | 分钟级 |
| 运行性能 | 接近原生 | 5%左右损失 |
| 磁盘占用 | MB | GB |
| 数量 | 成百上千 | 一般几十台 |
| 隔离性 | 进程级别 | 系统级(更彻底) |
| 操作系统 | 只支持Linux | 几乎所有 |
| 封装程度 | 只打包项目代码和依赖关系,共享宿主机内核 | 完整的操作系统 |
1.5 Docker 应用场景
-
应用程序打包和发布
-
应用程序隔离
-
持续基础
-
部署微服务
-
快速搭建测试环境
-
提供PaaS产品(平台即服务)
1.6 容器核心技术☆
通常来说,容器的核心技术主要包括Namespace和Cgroup两个特性,它们分别用于实现对资源的访问与使用隔离。
1. Namespace
Namespace又称为命名空间,它是Linux提供的一种内核级别环境隔离的方法,主要用于对系统资源的访问隔离。命名空间可以看作是系统中的安全沙盒,在其中的进程彼此独立。例如在A命名空间中有个pid为1的进程,而在B命名空间中也可以有一个pid为1的进程,它们之间互不影响。
目前Linux提供了以下几种命名空间类型:
| 类型 | 功能说明 |
|---|---|
| Mount namespaces | 提供文件系统挂载的隔离能力 |
| UTS namespaces | 提供主机名和域名隔离能力 |
| IPC namespaces | 提供进程间通信的隔离能力 |
| PID namespaces | 提供进程隔离能力 |
| Network namespaces | 提供网络隔离能力 |
| User namespaces | 提供用户和组隔离能力 |
2. Cgroup:
全称为Control Group(控制组),它是Linux内核提供的一个特性,可用于限制和隔离进程对于资源的使用,包括CPU、内存、磁盘 I/O和网络带宽。通过Cgroup技术,容器可以实现对于不同进程的资源分配。
Cgroup对于不同资源的管理通过各个子系统实现,子系统负责将特定类型的系统资源分配给特定的进程。
目前Cgroup具有以下子系统功能:
| 子系统 | 作用 |
|---|---|
| devices | 控制进程对于设备的访问权限 |
| cpuset | 分配指定的cpu和内存节点 |
| cpu | 控制cpu占用率 |
| cpuacct | 统计cpu使用情况 |
| memory | 限制内存的使用量 |
| freezer | 挂起Cgroup中的进程 |
| net_cls | 限制网络带宽 |
| net_prio | 设置进程的网络流量优先级 |
| huge_tlb | 限制Huge TLB的使用 |
| perf_event | 允许Perf工具基于Cgroup分组做性能监测 |
| blkio | 限制进程的块设备IO |
二、 Docker 版本区别及安装
2.1 Docker 版本
-
社区版(Community Edition,CE)
-
企业版(Enterprise Edition,EE)
2.2 Centos 安装Docker
若您安装过docker,需要先删掉,之后再安装依赖:
sudo yum remove docker docker-common docker-selinux docker-engine
安装依赖文件
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
添加Docker软件包源
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安装Docker CE
sudo yum -y install docker-ce docker-ce-cli containerd.io
指定安装Docker 版本
yum list docker-ce --showduplicates | sort -r
sudo yum -y install docker-ce-19.03.9 docker-ce-cli-19.03.9 containerd.io
开启路由转发
[root@node-0001 ~]# vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
]# for i in 31 32 33; do ssh 192.168.1.$i " echo net.ipv4.ip_forward = 1 >> /etc/sysctl.conf"; done
[root@node-0001 ~]# sysctl -p
]# for i in 31 32 33; do ssh 192.168.1.$i " sysctl -p"; done
设置ssh不检查
]# vim /etc/ssh/ssh_config
# 60行新添加
StrictHostKeyChecking no
启动Docker服务并设置开机启动
sudo systemctl start docker && systemctl enable docker
开放 FORWARD 默认规则
ExecStartPost(执行启动端口) /sbin/iptables(防火墙) 允许forward通过
[root@node-0001 ~]# vim /lib/systemd/system/docker.service
# 在 ExecStart 下面添加
ExecStartPost=/sbin/iptables -P FORWARD ACCEPT
[root@node-0001 ~]# systemctl daemon-reload
[root@node-0001 ~]# systemctl restart docker
[root@node-0001 ~]# iptables -nL FORWARD
设置镜像仓库加速
以阿里云为例,注册阿里云账号后,登录选择“容器镜像服务”,即可以看到阿里云提供的加速地址。按照操作对本地Docker进行简单配置,并在重启服务后使用,具体操作可参见云商说明 ,此处不细说。
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["根据实际生成地址填写"]
}
EOF
sudo systemctl daemon-reload && sudo systemctl restart docker
查看Docker 详细信息
docker info
-
$ sudo usermod -aG docker <user_name>
1. `Podman是RedHat的一款产品`
2. `旨在使用类似于Kubernetes的方法来构建、管理和运行容器,作为一款主流容器的可靠替代产品,它吸引了开发人员的关注。`
2. `自RHEL8起,Red Hat用CRI-O/Podman取代了Docker Daemon。为什么Red Hat想要摆脱Docker Daemon?这是因为使用Docker Daemon运行Docker有以下这些问题:`
- 单点故障问题,Docker Daemon一旦死亡,所有容器都将死亡
- Docker Daemon拥有运行中的容器的所有子进程
- 所有Docker操作都必须由具有跟root相同权限的用户执行
- 构建容器时可能会导致安全漏洞
4.Podman通过直接与Image Registry、Image和Container进行互动,而不是通过守护进程来解决以上问题。此外,Podman还允许用户在没有完全root 权限的情况下运行容器。
5.更棒的是,它还提供了与Docker兼容的指令,使用户可以很轻易的从原本的Docker指令切换到Podman。
'架构'
Docker使用守护进程,一个正在后台运行的程序,来创建镜像和运行容器。
Podman是无守护进程的架构,这意味着它可以在启动容器的用户下运行容器。
Docker有一个由守护进程引导的客户端——服务器逻辑架构;但Podman不需要此类守护进程。
'Root特权'
由于Podman没有守护进程来管理其活动,也无需为其容器分配Root特权。
Docker最近在其守护进程配置中添加了Rootless模式,但Podman首先使用了这种方法,并将其作为基本特性进行了推广。原因如下。
1.'安全'
Podman比Docker安全吗?
Podman允许容器使用Rootless特权。
Rootless容器被认为比Root特权的容器更安全。
在Docker中,守护进程拥有Root权限,这使得它们易成为攻击者的首选入侵点。
Podman中的容器默认情况下不具有Root访问权限,这在Root级别和Rootless级别之间添加了一个自然屏障,提高了安全性。不过,Podman可以同时运行Root容器和Rootless容器。
2.'Systemd'
如果没有守护进程,Podman需要另一个工具来管理服务并支持后台运行的容器。
Systemd为现有容器创建控制单元或用来生成新容器。
Systemd还可以与Podman集成,允许它在默认情况下运行启用了Systemd的容器,从而无需进行任何修改。
通过使用Systemd,供应商可以将他们的应用程序封装为容器用来安装、运行和管理,因为现在大多数应用程序都是通过这种方式打包和交付的。
3.'构建镜像'
作为一款自给自足的工具,Docker可以自己构建容器镜像。
Podman则需要另一种名为Buildah的工具的辅助,该工具充分体现了它的特殊性:它是为构建镜像而设计的,而不是为构建容器而生。
4.'Docker Swarm'
Podman不支持Docker Swarm,这可能会在某些项目中被刨除在外,因为使用Docker Swarm命令会产生一个错误。
然而,Podman最近增加了对'Docker Compose'的支持,使其与Swarm兼容,从而克服了这个限制。当然,Docker由于其原生的特性,与Swarm当然融合得很好。
5.'All in one(一体化) vs 模块化'
也许这就是这两种技术的关键区别:
Docker是一个独立的、强大的工具,在整个循环中处理所有的容器化任务,有优点也有缺点。
Podman采用模块化的方法,依靠专门的工具来完成特定的任务。
可视化图形工具Portainer
Portainer介绍
Portainer是一个可视化的容器镜像的图形管理工具,利用Portainer可以轻松构建,管理和维护Docker环境。 而且完全免费,基于容器化的安装方式,方便高效部署。
官方站点:
安装Portainer
官方安装说明:
[root@ubuntu1804 ~]#docker search portainer |head -n 3
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
portainer/portainer Making Docker management easy. https://porta… 1569
portainer/agent An agent used to manage all the resources in… 54 0
[root@ubuntu1804 ~]#docker pull portainer/portainer
[root@ubuntu1804 ~]#docker volume create portainer_data
portainer_data
[root@ubuntu1804 ~]#docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer
[root@ubuntu1804 ~]#docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
20db26b67b79 portainer/portainer "/portainer" 5 seconds ago Up 4 seconds 0.0.0.0:8000->8000/tcp, 0.0.0.0:9000->9000/tcp portainer
登录和使用Portainer
用浏览器访问:http://localhost:9000
三、 Docker 镜像与容器解析
1.bootfs和rootfs
-
通常而言,Linux的操作系统由两类文件系统组成:bootfs(boot file system)和rootfs(root file system),它们分别对应着
-
bootfs层主要为系统内核文件,这层的内容是无法修改的。当我们的系统在启动时会加载bootfs,当加载完成后整个内核都会存到内存中,然后
-
而rootfs层则包含了系统中常见的目录和文件,如/bin,/etc,/proc等等。
Docker的镜像技术可以使用宿主机的bootfs层,这使得
在不同Linux发行版本中,
如果是对内核版本的要求的程序,可能会因此受到影响。
2.镜像结构
Docker镜像采用分层的结构,由一些松耦合的只读层堆叠而成,并对外展示为一个统一的对象
所有的镜像都开始于一个基础的镜像层,当我们进行修改或内容添加时,会在镜像层上面创建新的一层。
最底层通常为基础层镜像,然后再层层叠加上来,
镜像做为所有镜像层的组合,如果镜像中有相同路径的文件,
如下图所示,由于第二层的文件2与第一层具有相同的文件路径,则镜像将以第二层的文件2内容进行展示,第一层只有文件1会被显示。
我们再来回顾一下前面镜像拉取时的输出内容,Pull complete结尾的每一行代表镜像中某个被拉取的层,每个层级通过一个唯一的ID进行标识。
$ docker pull nginx:1.20
1.20: Pulling from library/nginx
5eb5b503b376: Pull complete
cdfeb356c029: Pull complete
d86da7454448: Pull complete
7976249980ef: Pull complete
8f66aa6726b2: Pull complete
c004cabebe76: Pull complete Digest: sha256:02923d65cde08a49380ab3f3dd2f8f90aa51fa2bd358bd85f89345848f6e6623Status: Downloaded newer image for nginx:1.20
docker.io/library/nginx:1.20
镜像层的松耦合代表着它不属于某个镜像独有,
如下所示,当我们拉取另一个版本的Nginx镜像时,其中ID号为5eb5b503b376的层已经存在,则会显示为Already exists,直接使用此镜像层。
$ docker pull nginx:1.211.21:
Pulling from library/nginx
5eb5b503b376: Already exists
1ae07ab881bd: Pull complete
78091884b7be: Pull complete
091c283c6a66: Pull complete
55de5851019b: Pull complete
b559bad762be: Pull complete Digest: sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767Status: Downloaded newer image for nginx:1.21
docker.io/library/nginx:1.21
3.容器层
我们前面说到镜像层是只读模板,那么当我们使用镜像生成容器时,为什么又能写入数据呢?这个问题的答案涉及到一个概念:容器层。
当容器启动时,会有一个新的
当我们对容器进行操作时,底层的工作原理如下:
4.联合文件系统
关于镜像与容器功能的实现,依赖其使用了联合文件系统(UnionFS)技术,这是一种分层、轻量级并且高性能的文件系统。Docker 目前支持的联合文件系统包括 OverlayFS, AUFS, VFS Device Mapper等,而默认的存储驱动为Overlay2。
关于它的详细技术细节不文不做过多介绍,有兴趣的朋友可自行查阅。
四、Docker 镜像管理
镜像命令格式
docker [command] [-parameter] [target]
1.常用命令列表
| 指令 | 描述 |
|---|---|
| images | 列出镜像 |
| build | 构建镜像来自Dockerfile |
| history | 查看镜像历史 |
| inspect | 显示一个或多个镜像详细信息 |
| pull | 下载镜像,从镜像仓库拉取镜像 |
| push | 推送一个镜像到镜像仓库 |
| rmi | 删除一个或多个镜像 |
| prune | 移除未使用的镜像。没有被标记或被任何容器引用的 |
| tag | 创建一个引用源镜像标记目标镜像 |
| export | 导出容器文件系统到tar归档文件 |
| import | 导入容器文件系统tar归档文件创建镜像 |
| save | 导出镜像,命令使用参数 -o指定导出镜像到文件中。 |
| load | 存入镜像,使用docker [image] load命令即可将文件导入到该机器的镜像库中。 |
| search | 在下载之前我们可以使用docker search命令搜索该站点上的镜像仓库 |
2.镜像列表信息
-
REPOSITORY:表示镜像的仓库源及名称
-
TAG:镜像的标签
-
IMAGE ID:镜像ID
-
CREATED:镜像创建时间
-
SIZE:镜像大小
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-new 2.0 2c99e0f4fc85 2 minutes ago 162MB
nginx-new 1.0 c478994adb80 6 minutes ago 162MB
nginx latest 605c77e624dd 3 months ago 141MB
$ docker search nginx
四、Docker 容器管理
4.1 创建容器参数选项
docker [命令] [-参数] [target]
| 参数 | 描述 |
|---|---|
| -i, –interactive | 交互式 |
| -t, –tty | 分配一个伪终端 |
| -d, –detach | 运行容器到后台 |
| -e, –env | 设置环境变量 |
| -p, –publish list | 发布容器端口到主机 |
| -P, –publish-all | 发布容器所有EXPOSE的端口到宿主机随机端口 |
| –name string | 指定容器名称 |
| -h, –hostname | 设置容器主机名 |
| –ip string | 指定容器IP,只能用于自定义网络 |
| –network | 连接容器到一个网络 |
| –mount mount | 将文件系统附加到容器 |
| -v, –volume list | 绑定挂载一个卷 |
| –restart string | 容器退出时重启策略,默认no,可选值:[always|on-failure] |
4.2 容器资源限制
| 选项 | 描述 |
|---|---|
| -m,–memory | 容器可以使用的最大内存量 |
| –memory-swap | 允许交换到磁盘的内存量 |
| –memory-swappiness=<0-100> | 容器使用SWAP分区交换的百分比(0-100,默认为-1) |
| –oom-kill-disable | 禁用OOM Killer |
| --cpus | 可以使用的CPU数量 |
| –cpuset-cpus | 限制容器使用特定的CPU核心,如(0-3, 0,1) |
| –cpu-shares | CPU共享(相对权重) |
使用示例:
-
内存限额:
-
允许容器最多使用500M内存和100M的Swap,并禁用OOM Killer:
-
docker run -d --name nginx03 --memory="500m" --memory-swap=“600m" --oom-kill-disable nginx
-
CPU限额:
-
允许容器最多使用一个半的CPU:
docker run -d --name nginx04 --cpus="1.5" nginx -
允许容器最多使用50%的CPU:
docker run -d --name nginx05 --cpus=".5" nginx
-
4.3 容器常用命令
docker [命令] [-参数] [target]
| 选项 | 描述 |
|---|---|
| ps | 列出容器 |
| run | 运行一个容器 |
| inspect | 查看一个或多个容器详细信息 |
| exec | 在运行容器中执行命令 |
| commit | 创建一个新镜像来自一个容器 |
| cp | 拷贝文件/文件夹到一个容器 |
| logs | 获取一个容器日志 |
| port | 列出或指定容器端口映射 |
| top | 显示一个容器运行的进程 |
| stats | 显示容器资源使用统计 |
| stop/start/restart | 停止/启动一个或多个容器 |
| rm | 删除一个或多个容器 |
4.4 容器启动运行流程
第一步:我们通过客户端向Docker Server(服务器)发送了一条docker run的命令。 第二步:Docker Server会查看本地是否有对应的镜像,如果有的话直接进入第四步,否则进入第三步。 第三步:Docker 会去镜像仓库中下载对应的image到本地。 第四步:拥有了镜像,Docker将镜像运转起来,成为容器,这其中最重要的会为运行起来的容器分配相应的运行需要的
五、Docker容器数据存储
5.1 将数据从宿主机挂载到容器中的三种方式
Docker提供三种方式将数据从宿主机挂载到容器中:
-
volumes:(卷)Docker管理
-
bind mounts-:(绑定挂载)将宿主机上的任意位置的文件或者目录挂载到容器中。将宿主机文件共享到Docker容器
-
tmpfs:(临时存储 ) 挂载存储在主机系统的内存中,而不会写入主机的文件系统。如果不希望将数据持久存储在任何位置,可以使用tmpfs,同时避免写入容器可写层提高性能。
Docker的存储可分为容器存储 、持久化存储和临时存储三种方式
5.2 Volume
使用volumes的方式很简单,先创建volume,然后在启动容器时进行挂载。
管理卷:
$ docker volume create -d local myvolume
$ docker volume ls
DRIVER VOLUME NAME
local myvolume
$ docker volume inspect myvolume
[
{
"CreatedAt": "2022-06-05T23:22:58-04:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myvolume/_data",
"Name": "myvolume",
"Options": {},
"Scope": "local"
}
用卷创建一个容器:
$ docker run -d --name=nginx-test --mount type=volume,source=myvolume,target=/data nginx
$ docker run -d --name=nginx-test -v myvolume:/data nginx
先在该目录中创建一个文件
$ docker exec -it nginx touch /data/test.txt
然后,查看宿主机上volume目录情况,可看到文件已保存在该目录中。现在,我们可以很方便的对数据进行
$ ls -l /var/lib/docker/volumes/myvolume/_data/
total 0
-rw-r--r--. 1 root root 0 Jun 6 04:28 test.txt
清理卷:
$ docker volume rm myvolume
$ docker stop nginx-test
$ docker rm nginx-test
注意:
-
如果没有指定卷,自动创建。
-
建议使用--mount,更通用。
-
volumes除了支持挂载本机的目录外,也支持挂载到远程目录,如NFS服务器等。
Volume特点:
-
多个运行容器之间共享数据,多个容器可以同时挂载相同的卷。
-
当容器停止或被移除时,该卷依然存在。
-
当明确删除卷时,卷才会被删除。
-
将容器的数据存储在远程主机或其他存储上(间接)。
-
将数据从一台Docker主机迁移到另一台时,先停止容器,然后备份卷的目录(/var/lib/docker/volumes/)。
5.3 Bind Mounts
用卷创建一个容器:
$ docker run -d -it --name=nginx-test --mount type=bind,src=/app/wwwroot,dst=/usr/share/nginx/html nginx
$ docker run -d -it --name=nginx-test -v /app/wwwroot:/usr/share/nginx/html nginx
$ docker run -d --name=nginx-test -v /data/nginx/nginx.conf:/etc/nginx/nginx.conf nginx
验证绑定:
docker inspect nginx-test
清理:
docker stop nginx-test
docker rm nginx-test
注意:
-
如果源文件/目录没有存在如果挂载目标在容器中非空目录,则该目录现有内容将被隐藏。
-
不会自动创建,会抛出一个错误。
-
使用bind mount 挂载的目录,无法在docker命令行进行管理,所以通常建议优先使用volumes模式。
Bind Mounts特点:
-
从主机共享配置文件到容器。默认情况下,挂载主机/etc/resolv.conf到每个容器,提供DNS解析。
-
在Docker主机上的开发环境和容器之间共享源代码。例如,可以将Maven target目录挂载到容器中,每次在Docker主机上构建Maven项目时,容器都可以访问构建的项目包。
-
当Docker主机的文件或目录结构保证与容器所需的绑定挂载一致时。
5.4 tmpfs
在容器保存文件时,除了容器层存储以及持久化的存储外,还有第三种方式:tmpfs mounts。
与持久化存储相反,tmpfs mount是临时性的,存储内容会保存在内存中。当容器停止时,tmpfs的挂载将被移除,写入的文件也不会存在。这适用于保存在容器运行过程产生的敏感文件,你
支持tmpfs mounts 挂载的选项有 --mount 和 --tmpfs,它们的用法分别如下:
--mount选项
docker run -d --name nginx --mount type=tmpfs,target=/data nginx
--tmpfs选项
docker run -d --name nginx --tmpfs /data nginx:1.20-alpine
查看容器信息,可看到该目录显示的类型为tmpfs,证明挂载成功。
$ docker container inspect nginx...... "Mounts": [ { "Type": "tmpfs", "Source": "", "Destination": "/data",......
我们试着在容器挂载目录中写入一个文件
$ docker exec -it nginx touch /data/test.txt
$ docker exec -it nginx ls -l /data
total 0-rw-r--r-- 1 root root 0 Jun 9 08:01 test.txt
当容器停止后,该挂载的内存空间将会被释放,此时重新启动容器将分配新的空间。
$ docker stop nginx$ docker start nginx
$ docker exec -it nginx ls -l /datatotal 0
六、Docker容器网络
6.1 网络模式
-
bridge -–net=bridge 默认网络,Docker启动后创建一个
-
host- –net=host 容器不会获得一个独立的network namespace,而是
-
none -–net=none 获取独立的network namespace,但不为容器进行任何网络配置,需要我们手动配置。
-
container -–net=container:Name/ID 与指定的容器使用同一个network namespace,具有同样的网络配置信息,
-
自定义网络 与默认的bridge原理一样,但自定义网络具备内部DNS发现,可以通过容器名容器之间网络通信。
四类网络模式:
| 选项 | 描述 |
|---|---|
Bridge |
默认为该模式,此模式会为每一个容器分配,设置IP等,并将容器连接到一个docker0的虚拟网桥,通过docker0网桥以及iptables nat表配置与宿主机通信。 |
Host |
和宿主机共用一个Network Namespace(容器不会虚拟出自己的网卡,配置主机的IP等,而是使用宿主机的IP和端口; |
None |
该模式关闭了网络功能 |
Container |
创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP,端口范围; |
| 自定义网络 |
6.2 容器网络访问详解
Docker 容器默认使用 bridge 模式的网络。其特点如下:
-
使用一个 linux bridge,默认为 docker0
-
使用 veth 对,一头在容器的网络 namespace 中,一头在 docker0 上
-
该模式下Docker Container不具有一个公有IP,因为宿主机的IP地址与veth pair的 IP地址不在同一个网段内
-
Docker采用 NAT 方式,将容器
-
外界访问容器内的服务时,需要访问宿主机的
-
-
NAT 模式由于是在三层网络上的实现手段,故肯定会
-
容器拥有独立、隔离的网络栈;让容器和宿主机以外的世界
-host模式: 与宿主机共享网络(容器直连真机)
-container模式: 多个容器互通
-none模式: 无网络模式
-bridge模式: 默认桥接模式
-自定义网络: 自由创建桥接网络或者over lay网络
1. bridge桥接(默认网络模式)
就跟虚机的net模式一样,把物理机为
-
在安装完Docker后会自动创建三个网络,
docker network ls命令可查看相关的网络信息。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
ad87b0b5afaa bridge bridge local
19bd38b4d728 host host local
a83dd07d2ba1 none null local
-
brctl show 命令查看网桥信息,可以看到在没有启动任何容器的情况下,docker0的 "
interfaces" 处为空,表明该网桥目前未挂载任何接口。
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02425cf9d61c no
-
现在,我们启动一个容器后,再次来查看网桥情况。可以看到,当前网桥已经挂载了一个接口,名称为veth1ec2b44。
$ docker run -d --name nginx -p 80:80 nginx:1.20-alpine
07a46c03d17b40545a090dab60eac9a75bfa8050c572ae2c330ca98700ce68d5
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02428b368c00 no veth1ec2b44
-
我们查看一下容器里面的网络配置,可以看到容器的网卡名称为
$ docker exec nginx ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
Veth Pair就如一根网线的两端,其中一端收到的网络包会从另一端出去,这使得连接到同一个网
当bridge网络被创建时,系统会为其分配一个子网段,用于容器使用。当容器接入时,会从IP池中分配IP给到容器网卡,而容器的网关则会指向bridge,也即是docker0的IP地址。
2. host本机网络
相当于VMware中的桥接模式,与宿主机在同一个网络中,但没有独立的IP地址。
在启动容器时使用--network host指定为本机网络,则容器将与宿主机共享网络栈,此时容器会使用主机的IP以及其他网络配置。
$ docker run -d --network host --name nginx2 nginx:1.20-alpine
6f2023e6e714f9bb212bca9aec12dd7c3befd51a01d28216cea12c64136f6924
$ docker exec -it nginx2 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 00:0c:29:7b:a8:71 brd ff:ff:ff:ff:ff:ff
inet 192.168.214.112/24 brd 192.168.214.255 scope global dynamic noprefixroute ens33
valid_lft 2592730sec preferred_lft 2592730sec
inet6 fe80::f992:e993:bd8e:2de6/64 scope link noprefixroute
valid_lft forever preferred_lft forever
使用host网络时,对容器做端口映射将会无效, 容器会忽略 -p 所指定的端口,而直接在宿主机网络层面开启对应端口。例如,容器中如果开放了80端口,那么此时宿主机也将开放同样端口。
host网络相比bridge网络具有更高的性能,同时在容器有大量端口要开放的时候 ,也会省事很多。但同时也增加了不安全性,此时可在容器内对主机的网络栈进行操作,所以并不推荐使用。
3. null网络
none模式没有IP地址,无法连接外网,等于就是断网的状态,作用就是用于测试,生产环境一般不会用到这种。
null网络会禁用容器的网络栈,使得容器与外部隔离。在启动时通过 --network none 实现。此时,容器除lo外,将不再有其他网卡,完全与外部网络隔离。
$ docker run -d --network none --name nginx3 nginx:1.20-alpine
513e0ce37dab8dd6cb0b15fe0311af5ca7a050378ebcdb3b2c20d5a6bc39c2b5
$ docker exec -it nginx3 ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
这种场景一般使用很少,只有在某些不需要与外界联网,并且对安全要求非常高的程序才有可能用到。
4.container网络
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。 新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。 同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信
容器间网络通信
[root@node-0001 ~]# mkdir -p /var/{webroot,webconf}
[root@node-0001 ~]# cd kubernetes/docker-images
[root@node-0001 ~]# cp info.php info.html /var/webroot/ 网页文件
[root@node-0001 ~]# cp /usr/local/nginx/conf/nginx.conf /var/webconf/ 配置文件
[root@node-0001 ~]# vim /var/webconf/nginx.conf fastcgi配置
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
# 启动前端 nginx 服务对外共享,并映射共享目录和配置文件
[root@node-0001 ~]# docker run -itd --name nginx -p 80:80 \
-v /var/webconf/nginx.conf:/usr/local/nginx/conf/nginx.conf \
-v /var/webroot:/usr/local/nginx/html myos:nginx
# 启动后端 php 服务,并映射共享目录
[root@node-0001 ~]# docker run -itd --network=container:nginx \
-v /var/webroot:/usr/local/nginx/html myos:php-fpm
#--network=container:nginx 网络与nginx互通
#-v /var/webroot:/usr/local/nginx/html 网页文件映射到nginx的html中
# 验证服务
[root@node-0001 ~]# curl http://node-0001/info.html
<html>
<marquee behavior="alternate">
<font size="12px" color=#00ff00>Hello World</font>
</marquee>
</html>
[root@node-0001 ~]# curl http://node-0001/info.php
<pre>
Array
(
[REMOTE_ADDR] => 172.17.0.1
[REQUEST_METHOD] => GET
[HTTP_USER_AGENT] => curl/7.29.0
[REQUEST_URI] => /info.php
)
php_host: f705f89b45f9
1229
6.3 外部网络访问
docker0本身是作为宿主机的一个本地接口,因此,容器默认情况下可以访问到宿主机自身的网络。
但
在安装docker时,默认会启用Linux的包转发功能,如下:
$ sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
查看宿主机iptable的nat表上面的POSTROUTING链规则,可看到有一条转发规则。
$ sudo iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 71 packets, 5396 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
该规则的作用是当docker0收到来自容器网段(172.17.0.0/16)的网络包时,将其交给MASQUERADE处理。MASQUERADE与传统的SNAT相似,会将网络包的源IP替换为网卡IP,这样可以保证容器的网络包能够正常外出。
以本示例的容器为例,这里我们开放了80端口。当查看相关的iptable规则时,可看到多了一个关于80端口的DNAT规则。
$ docker run -d --name nginx -p 80:80 nginx:1.20-alpine
07a46c03d17b40545a090dab60eac9a75bfa8050c572ae2c330ca98700ce68d5
$ iptables -t nat -nvL DOCKER
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
......
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
该规则将访问到主机80端口的网络包,转发到容器地址(172.17.0.2)的80端口,以此实现外部网络对容器的访问。
此时,容器的整体网络架构如下图所示:
附录: Docker网络常用命令
1.network所有的子命令:
| 命令名称 | 说明 |
|---|---|
| 将容器连接到网络 | |
| 创建一个网络 | |
| 断开容器的网络 | |
| 显示一个或多个网络的详细信息 | |
| 列出所有网络 | |
| 删除所有未使用的网络 | |
| 删除一个或多个网络 |
选项
| 名称,简写 | 默认 | 说明 |
|---|---|---|
--alias |
为容器添加网络范围的别名 | |
--ip |
指定IP地址 | |
--ip6 |
指定IPv6地址 | |
--link |
添加链接到另一个容器 | |
--link-local-ip |
添加容器的链接本地地址 |
1. 创建网络
除了Docker默认生成的网络外,用户也可以创建自定义的网络。docker network create命令用于创建新的网络。
示例:此处创建一个名为mynet的bridge网络。
在此处指定DRIVER。 如果不指定–driver选项,该命令将为您自动创建一个桥接网络。 当安装Docker Engine时,会自动创建桥接网络。 该网络对应于Engine传统依赖的docker0网桥。 当启动使用docker run运行新容器时,它将自动连接到此桥接网络。不能删除此默认网桥,但可以使用network create命令创建新的网络。
docker network create [OPTIONS] NETWORK
1
| 名称,简写 | 默认 | 说明 |
|---|---|---|
--attachable |
false |
启用手动容器安装 |
--aux-address |
map[] |
网络驱动程序使用的辅助IPv4或IPv6地址 |
--driver, -d |
bridge |
驱动程序管理网络 |
--gateway |
用于主子网的IPv4或IPv6网关 | |
--internal |
false |
限制对网络的外部访问 |
--ip-range |
从子范围分配容器ip |
|
--ipam-driver |
default |
IP地址管理驱动程序 |
--ipam-opt |
map[] |
设置IPAM驱动程序的具体选项 |
--ipv6 |
false |
启用IPv6网络 |
--label |
在网络上设置元数据 | |
--opt, -o |
map[] |
设置驱动程序特定选项 |
--subnet |
表示网段的CIDR格式的子网 |
$ docker network create -d bridge mynet
592da9b6f8197cb7f11bae42f83f4429d9a97371a4aaccd3701d2998181763e8
2. 列出所有网络
docker network ls命令用于列出当前所有网络。
示例:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
12bb6c359b1c bridge bridge local
19bd38b4d728 host host local
12e7db06ad9b mynet bridge local
a83dd07d2ba1 none null local
3. 查看网络详情
docker network inspect命令可查看一个网络的详细信息,包括接入的容器、网络配置等。
示例:
$ docker network inspect mynet
[
{
"Name": "mynet",
"Id": "12e7db06ad9bf7aaaedb0cb33042ea48170d4ef2026da9964acba1cb984f441b",
"Created": "2022-05-30T03:40:51.977036849-04:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
......
4. 连接网络
# 示例1:docker network connect命令用于将容器连接到网络,通过该命令可以更改容器的接入网络。
$ docker network connect mynet nginx
# 示例2:启动容器时,使用–network标志将其连接到网络。 此示例将busybox容器添加到mynet网络:
$ docker run -itd --network=mynet busybox
指定高级选项
5. 删除网络
docker network rm 命令可用于删除指定网络。只有当网络没有容器连接时,才能正常删除。
示例:
$ docker network rm mynet
mynet
6.指定子网
使用–subnet选项直接指定子网络值。 在桥接网络上,只能创建单个子网:
$ docker network create --driver=bridge --subnet=192.168.0.0/16 br0
七、Dockerfile定制容器镜像
--commit 制作新镜像
添加了新内容后,commit打包镜像 docker commit 容器id 新镜像名称:标签 不建议用,会打包不必要的程序
--dockerfile 内容生成镜像
docker build根据Dockerfi le里的内容生成镜像
7.1 Dockerfile指令
| 指令 | 描述 |
|---|---|
| FROM | 指定基础镜像 |
| MAINTAINER | 指定镜像作者和联系方式 |
| RUN | 在容器中执行指定的命令 |
| EXPOSE | 声明容器将使用的端口 |
| ENV | 设置环境变量 |
| ADD | 将本地文件或目录复制到容器中 |
| COPY | 将本地文件或目录复制到容器中 |
| ENTRYPOINT | 配置容器启动时要运行的命令 |
| WORKDIR | 设置容器的工作目录 |
| CMD | 指定容器启动时要运行的命令 |
| VOLUME | 声明容器要使用的卷 |
| USER | 指定容器运行时的用户名或UID |
| ARG | 在构建镜像时传递参数 |
7.3 Build镜像
Usage: docker build [OPTIONS] PATH | URL | -[flags]Options:
-
-t, --tag list 镜像名称
-
-f, --file string 指定Dockerfile文件位置
-
最后面的 "." 表示当前目录为执行目录,命令将在当前目录中查看Dockerfile文件并执行。
docker build .
docker build -t shykes/myapp .
docker build -t shykes/myapp -f /path/Dockerfile /path
docker build -t shykes/myapp http://www.example.com/Dockerfile
7.4 构建Nginx基础镜像
CMD service nginx start
该命令会被包装为:
CMD [ "sh", "-c", "service nginx start"]
FROM centos:7
MAINTAINER weirel
RUN yum -y install gcc gcc-c++ make \
openssl-devel pcre-devel gd-devel \
iproute net-tools telnet wget curl --nogpgcheck && \
yum clean all && \
rm -rf /var/cache/yum/*
RUN wget http://nginx.org/download/nginx-1.19.10.tar.gz && \
tar zxvf nginx-1.19.10.tar.gz && \
cd nginx-1.19.10 && \
./configure --prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-http_stub_status_module && \
make -j 4 && make install && \
rm -rf /usr/local/nginx/html/* && \
echo "ok" >> /usr/local/nginx/html/index.html && \
cd / && rm -rf nginx-1.12.2* && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV PATH $PATH:/usr/local/nginx/sbin
COPY nginx.conf /usr/local/nginx/conf/nginx.conf
WORKDIR /usr/local/nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
wget https://cunqi0105-1300757323.cos.ap-shanghai.myqcloud.com/configuration-file/nginx.conf
7.5 构建Tomcat基础镜像
FROM centos:7
MAINTAINER weirel
ENV VERSION=8.5.65
RUN yum -y install java-1.8.0-openjdk wget curl unzip iproute net-tools --nogpgcheck && \
yum clean all && \
rm -rf /var/cache/yum/*
RUN wget https://mirrors.bfsu.edu.cn/apache/tomcat/tomcat-8/v8.5.65/bin/apache-tomcat-8.5.65.tar.gz && \
tar zxf apache-tomcat-${VERSION}.tar.gz && \
mv apache-tomcat-${VERSION} /usr/local/tomcat && \
rm -rf apache-tomcat-${VERSION}.tar.gz /usr/local/tomcat/webapps/* && \
mkdir /usr/local/tomcat/webapps/test && \
echo "ok" > /usr/local/tomcat/webapps/test/status.html && \
sed -i '1a JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom"' /usr/local/tomcat/bin/catalina.sh && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV PATH $PATH:/usr/local/tomcat/bin
WORKDIR /usr/local/tomcat
EXPOSE 8080
CMD ["catalina.sh", "run"]
7.6 构建PHP基础镜像
FROM centos:7
MAINTAINER weirel
RUN yum -y install epel-release --nogpgcheck && \
yum -y install gcc gcc-c++ make gd-devel libxml2-devel \
libcurl-devel libjpeg-devel libpng-devel openssl-devel \
libmcrypt-devel libxslt-devel libtidy-devel autoconf \
iproute net-tools telnet wget curl --nogpgcheck && \
yum clean all && \
rm -rf /var/cache/yum/*
RUN wget http://docs.php.net/distributions/php-7.3.7.tar.gz && \
tar zxvf php-7.3.7.tar.gz && \
cd php-7.3.7 && \
./configure --prefix=/usr/local/php \
--with-config-file-path=/usr/local/php/etc \
--enable-fpm --enable-opcache \
--with-mysql --with-mysqli --with-pdo-mysql \
--with-openssl --with-zlib --with-curl --with-gd \
--with-jpeg-dir --with-png-dir --with-freetype-dir \
--enable-mbstring --with-mcrypt --enable-hash && \
make -j 4 && make install && \
cp php.ini-production /usr/local/php/etc/php.ini && \
cp sapi/fpm/php-fpm.conf /usr/local/php/etc/php-fpm.conf && \
sed -i "90a \daemonize = no" /usr/local/php/etc/php-fpm.conf && \
mkdir /usr/local/php/log && \
cd / && rm -rf php* && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV PATH $PATH:/usr/local/php/sbin
COPY php.ini /usr/local/php/etc/
COPY php-fpm.conf /usr/local/php/etc/
WORKDIR /usr/local/php
EXPOSE 9000
CMD ["php-fpm"]
wget https://cunqi0105-1300757323.cos.ap-shanghai.myqcloud.com/configuration-file/php-fpm.conf
wget https://cunqi0105-1300757323.cos.ap-shanghai.myqcloud.com/configuration-file/php.ini
7.7 构建Nginx前端项目
FROM nginx
MAINTAINER weirel
COPY ./dist /usr/share/nginx/html
EXPOSE 80
ENTRYPOINT ["nginx","-s","reload"]
7.7 构建java后端项目
FROM openjdk:8-jdk-alpine
MAINTAINER weirel
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
EXPOSE 8888
ENTRYPOINT ["java","-jar","/app.jar"]
7.8 搭建LNMP网站平台
1、自定义网络
docker network create lnmp
2、创建Mysql容器
docker run -d --name lnmp_mysql --net lnmp --mount src=mysql-vol,dst=/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=wordpress mysql:5.7 --character-set-server=utf8
3、创建PHP容器
docker run -d --name lnmp_php --net lnmp --mount src=wwwroot,dst=/wwwroot php:v1
4、创建Nginx容器
docker run -d --name lnmp_nginx --net lnmp -p 88:80 --mount src=wwwroot,dst=/wwwroot nginx:v1
八、企业级镜像仓库
8.1 Harbor概述
Habor是由VMWare公司开源的容器镜像仓库。事实上,Habor是在Docker Registry上进行了相应的企业级扩展,从而获得了更加广泛的应用,这些新的企业级特性包括:管理用户界面,基于角色的访问控制,AD/LDAP集成以及审计日志等,足以满足基本企业需求。
官方地址:https://vmware.github.io/harbor/cn/
| 组件 | 功能 |
|---|---|
| harbor-adminserver | 配置管理中心 |
| harbor-db | Mysql数据库 |
| harbor-jobservice | 负责镜像复制 |
| harbor-log | 记录操作日志 |
| harbor-ui | Web管理页面和API |
| nginx | 前端代理,负责前端页面和镜像上传/下载转发 |
| redis | 会话 |
| registry | 镜像存储 |
8.2 Harbor离线包部署
安装docker compose
wget -P /usr/local/bin https://cunqi0105-1300757323.cos.ap-shanghai.myqcloud.com/install-pkg/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
安装harbor
wget https://cunqi0105-1300757323.cos.ap-shanghai.myqcloud.com/install-pkg/harbor-offline-installer-v2.1.5.tgz
tar zxvf harbor-offline-installer-v2.1.5.tgz && cd harbor
cp harbor.yml.tmpl harbor.yml && vim harbor.yml
# 设置主机地址
hostname: IP地址
# 设置主机端口
http:
port: 80
# 开启https访问
https:(如果没有证书需要将https字段进行注释)
# 设置登录密码
harbor_admin_password: Harbor12345
# 设置存储目录
data_volume: /data
./prepare && ./install.sh
docker-compose ps
8.3 基本使用
1、配置http镜像仓库可信任
vim /etc/docker/daemon.json
{"insecure-registries":["harbor仓库地址"]}
systemctl restart docker
2、打标签
docker tag centos:6 harbor仓库地址/library/centos:6
3、上传
docker push harbor仓库地址/library/centos:6
4、下载
docker pull harbor仓库地址/library/centos:6
九、安全性分析
容器是基于操作系统实现的虚拟化,相比于传统的物理主机和虚拟机,在安全上会面临更多的风险。对于容器的使用者而言,学习并了解容器的安全相关知识是一项重要的工作。
一. 容器的安全威胁
在虚拟机环境,每台虚拟机都包含了一个独立的操作系统,可以单独地完成绝大部分事务处理,而只需要与宿主机之间保留非常少的交互接口即可
而在容器的环境中,由于容器只封装应用和依赖,这使得其必须使用主机的内核来进行运作。因此,只要获取到容器的权限即可实现对内核的访问,包括攻击。如果容器中的应用导致内核崩溃,那么整个主机系统也会跟着一起崩塌。
1. 镜像污染
目前,Docker Hub上面有着数量众多的第三方上传镜像,这些镜像质量参差不齐。在这其中,不排除有黑客故意上传包含了恶意程序的镜像,并伪装成业务镜像供用户下载使用。当用户误用此类镜像启动容器时,无疑会导致容器被入侵。因此,用户必须保证容器所使用的镜像是可受信任的,尽可能不使用非信任的第三方镜像。
2. 网络攻击
Docker默认使用bridge网络,该网络会创建一个虚拟网桥,连接在同一个网桥之间的容器可以互相访问。当某个容器被入侵时,黑客有可能顺着网络访问到宿主机中的其他容器。同时 ,攻击者也可以通过DDos等方式,攻击容器的服务来耗尽主机的资源,从而引起整个宿主机的崩溃。
3. 内核攻击
当黑客获取到容器权限时,由于共享内核的缘故,理论上可通过对内核的攻击来达到控制或影响主机的地步 。例如,Linux内核3.16以前的版本存在一个内存溢出漏洞CVE-2014-7822,由于splice系统调用在两个文件间拷贝数据时未检查拷贝大小,可溢出覆写内核数据。因此,本地未授权用户可利用此漏洞越界写内存,导致系统崩溃。
4. 操作不当
用户在配置容器时,由于某些错误操作可能会将主机置于危险的境地。如:在启动容器时将主机的根目录映射到容器中,那么理论上容器就可以对主机的文件系统进行任意修改,造成极大的安全风险。
$ docker run -d -v /:/host nginx
又或者在启用容器时使用特权模式(--privileged),此时Docker容器被允许可以访问主机上的所有设备,并可以获取大量设备文件的访问权限。
$ docker run -d --privileged nginx
5. 针对Docker Daemon的攻击
Docker使用的是C/S架构,作为服务端的Docker Daemon会接收客户端通过CLI或者REST API发送的命令,并执行对容器的相应操作。但请求不一定都是由可信任的用户发起的,攻击者可能通过伪造请求的方式,来达到欺骗Daemon端执行危险的操作。
6. Docker漏洞攻击
Docker本身属于应用程序,不可避免地会有出现代码的漏洞,这可能导致程序受到攻击。
例如:在2019年时,Docker被爆出来一个逃逸漏洞CVE-2019-5736。该漏洞导致攻击者可以通过特定的容器镜像或者exec操作获取到宿主机runc执行文件时的文件句柄,并修改掉runc的二进制文件,最终获取到宿主机的root执行权限。
二. Docker的安全防护
针对容器在安全方面存在的威胁,Docker社区也使用了不少安全措施来进行应对,以便能够让用户更安全地使用容器。
下面是Docke在安全上的主要防护策略:
1. Namespace
Namespace即命名空间,也被称为名称空间,这是Linux提供的一种内核级别的环境隔离功能,它的主要用途是对容器提供资源的访问隔离。Docker充分利用了Namespace的技术,使其达到尽可能地隔离容器之间以及对宿主机的影响。
目前Namespace支持多种资源的隔离,包括文件系统挂载 、主机名和域名、进程间通信 、进程访问、网络隔离、用户和组隔离等。
2. Cgroup
Cgroup全称为Control Group( 控制组),它也是Docker容器的重要特性。如果说Namespace是用于隔离,那么Cgroup则是限制容器对于资源的占用,如CPU、内存、磁盘/IO等。这个特性非常有用,它避免了某个容器因为被Ddos攻击或者自身程序的问题导致对资源的不断占用,并最终影响到宿主机及上面运行的其他容器,出现“雪崩”的灾难 。
3. Capability
Capability是Linux内核具有的一个强大特性,可以提供颗粒化的权限访问控制。默认情况下,Docker启动的容器对能力有严格限制,只允许使用内核的一部分能力,包括chown、net_bind_service、fowner、kill、setgid等。
事实上,在大部分情况下程序并不需要真正的root权限,容器只需要具备少数的能力即可。对于能力的颗粒化划分,能保证即使容器被入侵后,也无法获取本地的较高权限,可以进行的破坏有限。
另外,用户可以通过--cap-add和--cap-drop这两个参数来修改能力。如下面的例子,我们将容器的chown 能力去掉后,在容器里就无法改变文件的所有者了。
$ docker run -it --cap-drop=chown centos
[root@65a55b893d3b /]# touch /root/1.txt
[root@65a55b893d3b /]# ls -l /root/1.txt
-rw-r--r--. 1 root root 0 Jul 3 11:33 /root/1.txt
[root@65a55b893d3b /]# chown daemon /root/1.txt
chown: changing ownership of '/root/1.txt': Operation not permitted
4. Docker Daemon安全防护
Docker社区希望实现由非root权限来启动Docker Daemon,这对于Docker的安全性是一种较大的强化。但要解决这个问题面临着不小的困难,因为创建容器需要执行很多特权操作,如挂载文件系统、配置网络等。在19.03版本中Rootless模式作为实验性功能推出,并在20.10版本中毕业。该功能对于使用会有较多限制,如不支持overlay网络,AppArmor特性等。同时该模式对内核版本要求高,如果要使用默认的overlay2 存储驱动,需要至少5.11的内核版本。
同时,为了加强对服务端的保护,Docker的REST API(客户端用来与服务端通信的接口)在0.5.2之后,由原先绑定在127.0.0.1上的TCP套接字改为使用本地Unix套接字机制代替,因为前者更容易受到跨站请求伪造攻击。
$ netstat -lnpxActive UNIX domain sockets (only servers)Proto RefCnt Flags Type State I-Node PID/Program name Pathunix G2 [ ACC ] STREAM LISTENING 644610 1/systemd /var/run/docker.sock......
5. 其他安全机制
除了上面介绍的安全机制外,Docker还支持结合现有的安全软件或机制来增强自身的安全性,例如:AppArmor、SELinux、Seccomp等。
AppArmor和SElinux属于MAC (Mandatory Access Control)系统,AppArmor主要内置在Ubuntu、Debian等操作系统,而SELinux则预装在Redhat、Centos中。MAC系统可以通过定制化的安全策略来控制进程对于资源的访问。目前,在支持SElinux的系统上,Docker的SElinux使用不是默认打开 的,需要在启动Docker daemon时加上--selinux-enabled=true参数。而在支持AppArmor的系统上,对于Docker的安全防护功能是默认打开的。
Seccomp(secure computing mode)是一种Linux内核提供的安全特性,它可以用来限制容器的可执行操作。Seccomp以白名单或黑名单的方式限制进程进行系统调用,通过禁止进程调用不必要的系统功能来减少内核暴露的接口数量,从而降低内核的攻击面。Docker在启动容器时默认会启用Seccomp保护,可通过下面命令进行确认。默认的白名单规则仅保留了Linux中比较常见并且安全的系统调用,具有适度的安全性。
$ grep CONFIG_SECCOMP= /boot/config-$(uname -r)CONFIG_SECCOMP=y
有关更多安全相关的内容,读者可自行查看Docker官网,在此不过多列举。
总结:
虽然容器面临着不少安全威胁,但用户并不需要为此过多担忧。就如本文所述,Docker在安全方面已经有着非常多的应对措施,可以很好地保证容器的安全性和可靠性。
目前,Docker作为生产环境的容器化产品,已在各大企业中被广泛使用。
十、容器安全的最佳实践
一. 容器配置
-
禁止使用特权容器
使用--privileged启动容器时,会将所有内核功能赋予容器,从而覆盖Capability的能力限制。在这种情况下,容器可执行主机层面能做的大部分事情,从而给系统带来较大的安全隐患,所以应该被禁止。
$ docker run -d --privileged nginx
-
限制容器的资源使用
默认情况下,主机上的容器对于资源的访问并没有受到限制。此时,当某个容器被攻击或者程序出错时,将可能因为过多占用资源而影响到主机和其他容器的运行。
因此,可以通过Cgroup的特性来设置容器的资源使用限制,这样得以最大程度减少因此类问题而产生的影响 。
示例1:限制CPU可以使用的最大核数为1 。
$ docker run -d --cpus 1 nginx
示例2:限制容器可以使用特定的CPU,如宿主机有4个CPU,我们只允许容器使用第一个CPU,可以设置如下 :
$ docker run -d --cpuset-cpus 0 nginx
示例3:限制容器最大内存使用量为512M。
$ docker run -d -m 512m nginx
-
隔离容器网络
Docker默认使用bridge网络,该网络会创建一个虚拟网桥,连接在同一个网桥之间的容器可以互相访问。为了提升网络安全性,建议不要依赖默认的docker0网络,而是通过自定义网络方式进行容器网络的划分,这样可保证对宿主机中的容器之间做到访问隔离。
另外,在主机层面也应尽可能进行网络划分,不同的主机运行不同安全性要求的容器,如开发环境、测试环境和生产环境的宿主机应该彼此隔离。
-
禁止挂载主机系统目录
主机中与系统相关的目录,包含 /boot、/dev、/etc、/proc、/sys、/usr等,应该明令禁止被挂载到容器中。尤其是在读写模式下,这种情况会导致容器具有对主机系统进行修改的能力,从而给主机系统带来极大的安全隐患。
遗憾的是,目前在Docker层面并没有相关的技术方案来限制此类行为,只能通过审计等手段进行发现。
-
将容器的根文件系统挂载为只读
将容器的根文件系统挂载为只读模式,这种模式可以避免对根文件系统带来任何的修改,从而保证了容器的安全性。当然,只读的模式也会带来不便 ,需要结合挂载存储来使用,将输出的内容写到持久化存储中。
配置只读的方式很简单,在启动容器时添加 --read only 选项,如下:
$ docker run -it --read-only nginx sh
# echo 'test' > /root/1.txt
sh: 2: cannot create /root/1.txt: Read-only file system
-
禁止共享主机的网络栈
在启动容器时,使用--network host 可以让容器与主机共享网络栈,此时,容器会使用主机的IP及其他的网络配置,并自动将容器的端口映射到主机。
这种方法虽然带来了一定的便利性,但也给容器带来了风险。在这种模式下,容器内可对主机的网络栈进行操作,所以并不建议使用。
$ docker run -d --network host nginx
二. 镜像管理
1.禁止在镜像中存储机密信息
容器中程序在正常运行的过程中,通常需要使用到一些机密信息,如数据库账号密码、access token等。诸如此类的敏感信息,不应被保存到镜像中,不然会造成机密信息泄露风险。
可以使用配置中心等方案,将这些信息移到外部进行管理。
2.选用最小化的基础镜像
在使用Dockerfile构建应用镜像时,需要选择一个镜像来作为基础镜像。对于该镜像的选择,应避免使用功能大而全的镜像,而采用最小化满足的模式。
这样可以带来两个好处:
-
最大程度减少安全漏洞。
-
减少对于资源的占用。
3.扫描并验证镜像
在 2019 年时,作为安全公司的Snyk 发现,10大最流行的 Docker 镜像中,每个镜像至少有 30 个安全漏洞。这足以引起我们对于镜像安全的警觉。

通过对Docker镜像进行漏洞扫描,可以让开发和运维人员清楚镜像的安全状态,并采取措施修复发现的问题,从而实现更安全的部署。
目前支持镜像扫描的开源方案有Docker scan、Clair、Anchore等,其中Docker Scan已集成到最新的Docker版本中,可以开箱即用。
\4. 使用多阶段构建
在旧的版本中,构建镜像的操作通常在一个Dockerfile流程中完成,在这种模式下构建的镜像会包含很多冗余的文件,如源码文件、下载的依赖包、打包产生的临时文件等。
为了解决这个问题,Docker在17.05 版本开始支持使用多阶段构建(Multi-stage builds)。使用这种模式构建镜像时,我们可以第一阶段完成代码的打包等工作流程,然后在第二阶段选择合适的运行镜像,并将上个阶段生成的包拷贝到镜像中配置运行。
多阶段构建具有速度快、镜像体积更小、安全性更高等特点,建议采用此种方式来进行镜像的构建 。
示例:
#阶段一:编译打包
FROM maven:3.5.0-jdk-8-alpine AS builder
ADD ./pom.xml pom.xml
ADD ./src src/
RUN mvn clean package
# 阶段二:配置运行
From openjdk:8-jre-alpine
COPY --from=builder target/my-app-1.0-SNAPSHOT.jar my-app-1.0-SNAPSHOT.jar
EXPOSE 8080
CMD ["java", "-jar", "my-app-1.0-SNAPSHOT.jar"]
三. 管理规范
-
限制用户对容器主机的访问
运行容器的主机应该存放在安全、可靠的环境中,如IDC机房,这可以在物理上保证机器的安全性。同时 ,做好系统登录权限的管控,只允许可信任的用户登录访问主机,这样能够减少由于人为影响而出现的故障。
-
定期更新Docker版本
过时的版本由于已发现的漏洞,容易受到安全攻击。新版本通常会修复旧版本出现的Bug和程序错误,有利于更好地保证容器安全。作为容器的管理员,需要定期进行Docker版本的更新,并维持着较新的版本。
-
完善容器监控
如何合理有效的利用好监控是容器管理员的重要工作,一套完善的监控系统有利于帮助我们及时发现容器的问题,例如资源负载高、容器退出等。
关于容器的监控,目前比较常用的方案有cAdvisor+Prometheus+Grafana+Alertmanager组合,该方案集成监控、展示和告警等系列功能,可以有效发现容器问题。
相关的内容本文不做介绍,有需要的读者可查看笔者写的

浙公网安备 33010602011771号