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 虚拟机

容器:共享宿主机的内核,进程隔离,打包了文件系统

虚机:有操作系统,系统级隔离

image-20220817160814740

  • 虚拟机(重量级)

    • 基础设施(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进行简单配置,并在重启服务后使用,具体操作可参见云商说明 ,此处不细说。

image-20220817160747780

 

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
  1. $ sudo usermod -aG docker <user_name>

 

Podman是什么?它与Docker有什么区别?

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环境。 而且完全免费,基于容器化的安装方式,方便高效部署。

官方站点:https://www.portainer.io/

安装Portainer

官方安装说明:https://www.portainer.io/installation/

[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,当加载完成后整个内核都会存到内存中,然后系统会将bootfs卸载掉

  • 而rootfs层则包含了系统中常见的目录和文件,如/bin,/etc,/proc等等。

image-20220817160733375

Docker的镜像技术可以使用宿主机的bootfs层,这使得镜像本身只需要封装rootfs层所需要的文件和工具即可。因此,镜像可以根据需要进行定制化封装,减少占用的存储空间,如部分极精简的镜像只有几MB大小。

在不同Linux发行版本中,它们之间的主要区别在于rootfs层,比如ubuntu使用apt管理软件,而Centos使用yum方式。而在内核层面,两者的差别并不大。

如果是对内核版本的要求的程序,可能会因此受到影响。

2.镜像结构

Docker镜像采用分层的结构,由一些松耦合的只读层堆叠而成,并对外展示为一个统一的对象

所有的镜像都开始于一个基础的镜像层,当我们进行修改或内容添加时,会在镜像层上面创建新的一层。

最底层通常为基础层镜像,然后再层层叠加上来,比如安装一个Python软件,此时会在基础层上面添加一个新的层,上面包含了我们所安装的Python程序。

image-20220817160723249

镜像做为所有镜像层的组合,如果镜像中有相同路径的文件,则上层镜像会覆盖下层镜像的内容,最终展示为所有层的数据汇总。

如下图所示,由于第二层的文件2与第一层具有相同的文件路径,则镜像将以第二层的文件2内容进行展示,第一层只有文件1会被显示。

image-20220817160704991

我们再来回顾一下前面镜像拉取时的输出内容,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

镜像层的松耦合代表着它不属于某个镜像独有,当不同镜像包含相同的层时,系统只会存储该层的一份内容,这是Docker镜像的重要特点,这样的好处有利于减少存储空间的占用

如下所示,当我们拉取另一个版本的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.容器层

我们前面说到镜像层是只读模板,那么当我们使用镜像生成容器时,为什么又能写入数据呢?这个问题的答案涉及到一个概念:容器层。

当容器启动时,会有一个新的可写层被加载到镜像的顶部,这一层通常被称为容器层。所有对容器的修改都会发生在容器层,只有容器层是可写入的,容器层以下的镜像层都是只读的

image-20220817161344698

当我们对容器进行操作时,底层的工作原理如下:

  • 读取文件:当容器需要读取文件时,会先在容器层寻找,如果没有发现,则会从最上层的镜像层往下寻找,当找到文件后读取到内存使用。

  • 增加文件:当增加文件时,文件会直接写到最上面容器层,不会影响到镜像层内容。所以,当我们将容器删除时,容器中的文件也会随着消失。

  • 修改文件:此时,如果该文件是在容器层的,则会直接修改。否则的话,Docker会从上往下依次在各层镜像中查找此文件 ,当找到后将其复制到容器层中,并进行修改。这被称为容器的写时复制特性(Copy-on-Write),这个技术保证了我们对容器的修改不会影响到底层的镜像,也实现了一个镜像可以被多个容器共用。

  • 删除文件:当我们需要删除文件时,Docker也是由上往下寻找该文件 ,如果在容器层的文件会被直接删除,而在镜像层的文件则会被标记,此时在容器将不会再出现此文件,但镜像中的文件并不会做更改。

 

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 容器启动运行流程

在这里插入图片描述

image-20220817164815547

 

第一步:我们通过客户端向Docker Server(服务器)发送了一条docker run的命令。 第二步:Docker Server会查看本地是否有对应的镜像,如果有的话直接进入第四步,否则进入第三步。 第三步:Docker 会去镜像仓库中下载对应的image到本地。 第四步:拥有了镜像,Docker将镜像运转起来,成为容器,这其中最重要的会为运行起来的容器分配相应的运行需要的资源,如网络,存储,Cpu,内存等。

五、Docker容器数据存储

5.1 将数据从宿主机挂载到容器中的三种方式

Docker提供三种方式将数据从宿主机挂载到容器中:

  • volumes:(卷)Docker管理宿主机文件系统的一部分(/var/lib/docker/volumes)。保存数据的 ==>最佳方式。

  • bind mounts-:(绑定挂载)将宿主机上的任意位置的文件或者目录挂载到容器中。将宿主机文件共享到Docker容器

  • tmpfs:(临时存储 ) 挂载存储在主机系统的内存中,而不会写入主机的文件系统。如果不希望将数据持久存储在任何位置,可以使用tmpfs,同时避免写入容器可写层提高性能。

image-20220817165235808

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

注意:

  1. 如果没有指定卷,自动创建。

  2. 建议使用--mount,更通用。

  3. 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

注意:

  1. 如果源文件/目录没有存在如果挂载目标在容器中非空目录,则该目录现有内容将被隐藏。

  2. 不会自动创建,会抛出一个错误。

  3. 使用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启动后创建一个docker0网桥,默认创建的容器也是添加到这个网桥中。

  • 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 方式,将容器内部的服务监听的端口宿主机的某一个端口port 进行“绑定”,使得宿主机以外的世界可以主动将网络报文发送至容器内部

    • 外界访问容器内的服务时,需要访问宿主机的 IP 以及宿主机的端口 port

  • 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
  • 我们查看一下容器里面的网络配置,可以看到容器的网卡名称为eth0@if11,它与挂载到网桥的veth1ec2b44接口即是一对Veth Pair(一对虚拟接口)

$ 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就如一根网线的两端,其中一端收到的网络包会从另一端出去,这使得连接到同一个网

image-20220818144050153

当bridge网络被创建时,系统会为其分配一个子网段,用于容器使用。当容器接入时,会从IP池中分配IP给到容器网卡,而容器的网关则会指向bridge,也即是docker0的IP地址。

image-20220818150316704

 

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 网卡设备通信

img

 
node-0001
容器1
容器2
 
 
 
 
 
共享存储卷
PHP
Nginx
共享网络
用户
 

容器间网络通信

[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本身是作为宿主机的一个本地接口,因此,容器默认情况下可以访问到宿主机自身的网络。

当容器需要访问外部网络时,则需要宿主机做一层NAT转发

在安装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端口,以此实现外部网络对容器的访问。

此时,容器的整体网络架构如下图所示:

image-20220818164705313

附录: Docker网络常用命令

1.network所有的子命令:

命令名称 说明
docker network connect 将容器连接到网络
docker network create 创建一个网络
docker network disconnect 断开容器的网络
docker network inspect 显示一个或多个网络的详细信息
docker network ls 列出所有网络
docker network prune 删除所有未使用的网络
docker network rm 删除一个或多个网络

选项

名称,简写 默认 说明
--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

 

 

 

 

 

九、安全性分析

容器是基于操作系统实现的虚拟化,相比于传统的物理主机和虚拟机,在安全上会面临更多的风险。对于容器的使用者而言,学习并了解容器的安全相关知识是一项重要的工作。

链接参考

一. 容器的安全威胁

在虚拟机环境,每台虚拟机都包含了一个独立的操作系统,可以单独地完成绝大部分事务处理,而只需要与宿主机之间保留非常少的交互接口即可

image-20220823155925983

而在容器的环境中,由于容器只封装应用和依赖,这使得其必须使用主机的内核来进行运作。因此,只要获取到容器的权限即可实现对内核的访问,包括攻击。如果容器中的应用导致内核崩溃,那么整个主机系统也会跟着一起崩塌。

image-20220823160030226

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作为生产环境的容器化产品,已在各大企业中被广泛使用。

 

十、容器安全的最佳实践

一. 容器配置

  1. 禁止使用特权容器

使用--privileged启动容器时,会将所有内核功能赋予容器,从而覆盖Capability的能力限制。在这种情况下,容器可执行主机层面能做的大部分事情,从而给系统带来较大的安全隐患,所以应该被禁止。

 

$ docker run -d --privileged nginx

 

  1. 限制容器的资源使用

默认情况下,主机上的容器对于资源的访问并没有受到限制。此时,当某个容器被攻击或者程序出错时,将可能因为过多占用资源而影响到主机和其他容器的运行。

因此,可以通过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

 

  1. 隔离容器网络

Docker默认使用bridge网络,该网络会创建一个虚拟网桥,连接在同一个网桥之间的容器可以互相访问。为了提升网络安全性,建议不要依赖默认的docker0网络,而是通过自定义网络方式进行容器网络的划分,这样可保证对宿主机中的容器之间做到访问隔离。

另外,在主机层面也应尽可能进行网络划分,不同的主机运行不同安全性要求的容器,如开发环境、测试环境和生产环境的宿主机应该彼此隔离。

 

  1. 禁止挂载主机系统目录

主机中与系统相关的目录,包含 /boot、/dev、/etc、/proc、/sys、/usr等,应该明令禁止被挂载到容器中。尤其是在读写模式下,这种情况会导致容器具有对主机系统进行修改的能力,从而给主机系统带来极大的安全隐患。

遗憾的是,目前在Docker层面并没有相关的技术方案来限制此类行为,只能通过审计等手段进行发现。

 

  1. 将容器的根文件系统挂载为只读

将容器的根文件系统挂载为只读模式,这种模式可以避免对根文件系统带来任何的修改,从而保证了容器的安全性。当然,只读的模式也会带来不便 ,需要结合挂载存储来使用,将输出的内容写到持久化存储中。

配置只读的方式很简单,在启动容器时添加 --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

 

  1. 禁止共享主机的网络栈

在启动容器时,使用--network host 可以让容器与主机共享网络栈,此时,容器会使用主机的IP及其他的网络配置,并自动将容器的端口映射到主机。

这种方法虽然带来了一定的便利性,但也给容器带来了风险。在这种模式下,容器内可对主机的网络栈进行操作,所以并不建议使用。

$ docker run -d --network host  nginx

 

二. 镜像管理

1.禁止在镜像中存储机密信息

容器中程序在正常运行的过程中,通常需要使用到一些机密信息,如数据库账号密码、access token等。诸如此类的敏感信息,不应被保存到镜像中,不然会造成机密信息泄露风险。

可以使用配置中心等方案,将这些信息移到外部进行管理。

 

2.选用最小化的基础镜像

在使用Dockerfile构建应用镜像时,需要选择一个镜像来作为基础镜像。对于该镜像的选择,应避免使用功能大而全的镜像,而采用最小化满足的模式。

这样可以带来两个好处:

  1. 最大程度减少安全漏洞。

  2. 减少对于资源的占用。

 

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"]

 

 

三. 管理规范

  1. 限制用户对容器主机的访问

    运行容器的主机应该存放在安全、可靠的环境中,如IDC机房,这可以在物理上保证机器的安全性。同时 ,做好系统登录权限的管控,只允许可信任的用户登录访问主机,这样能够减少由于人为影响而出现的故障。

 

  1. 定期更新Docker版本

    过时的版本由于已发现的漏洞,容易受到安全攻击。新版本通常会修复旧版本出现的Bug和程序错误,有利于更好地保证容器安全。作为容器的管理员,需要定期进行Docker版本的更新,并维持着较新的版本。

  2. 完善容器监控

    如何合理有效的利用好监控是容器管理员的重要工作,一套完善的监控系统有利于帮助我们及时发现容器的问题,例如资源负载高、容器退出等。

    关于容器的监控,目前比较常用的方案有cAdvisor+Prometheus+Grafana+Alertmanager组合,该方案集成监控、展示和告警等系列功能,可以有效发现容器问题。

    相关的内容本文不做介绍,有需要的读者可查看笔者写的《Prometheus实战教程》 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2023-05-12 10:46  ~墨鱼~  阅读(54)  评论(0)    收藏  举报