在企业生产应用中,docker容器技术及k8s的编排管理工具的使用率越来越高,这项技术甚至已经改变了很多企业的架构与框架流程,因为容器技术的出现,可以将应用以集装箱的方式打包交付,使应用在不同的团队中共享,通过镜像的方式应用可以部署于任何环境中。这样避免了各团队之间的协作问题的出现,成为企业实现DevOps目标的重要工具,而且以容器方式交付的Docker技术支持不断地开发迭代,提升了产品开发和交付速度,极大的方便了业务的横向扩容。
  且与KVM虚拟化技术不同的是,Docker直接移植于Linux内核之上,通过运行Linux进程将底层设备虚拟隔离,这样系统性能的损耗也要比虚拟机低的多,几乎可以忽略。同时,Docker应用容器的启停非常高效,可以支持大规模的分布系统的水平扩展,真正给企业开发带来福音。

Docker简介

Docker 是什么

  首先 Docker 是一个在 2013 年开源的应用程序并且是一个基于 go 语言编写是一个开源的 PAAS 服务(Platform as a Service, 平台即服务的缩写), go 语言是由google 开发, docker 公司最早叫 dotCloud 后由于 Docker 开源后大受欢迎就将公司改名为 Docker Inc, 总部位于美国加州的旧金山, Docker 是基于 linux 内核实现, Docker 最早采用 LXC 技术(LinuX Container 的简写, LXC 是 Linux 原生支持的容器技术, 可以提供轻量级的虚拟化, 可以说 docker 就是基于 LXC 发展起来的,提供 LXC 的高级封装,发展标准的配置方法), 而虚拟化技术 KVM(Kernelbased Virtual Machine) 基于模块实现, Docker 后改为自己研发并开源的 runc 技术运行容器。

Dokcer与虚拟机技术对比

  Docker 相比虚拟机的交付速度更快, 资源消耗更低, Docker 采用客户端/服务端架构,使用远程 API 来管理和创建 Docker 容器,其可以轻松的创建一个轻量级的、 可移植的、自给自足的容器, docker 的三大理念是 build(构建)、ship(运输)、 run(运行), Docker 遵从 apache 2.0 协议,并通过(namespace 及cgroup 等)来提供容器的资源隔离与安全保障等,所以 Docke 容器在运行时不需要类似虚拟机(空运行的虚拟机占用物理机 6-8%性能)的额外资源开销,因此可以大幅提高资源利用率,总而言之 Docker 是一种用了新颖方式实现的轻量级虚拟机.类似于 VM 但是在原理和应用上和 VM 的差别还是很大的,并且 docker的专业叫法是应用容器(Application Container)。
| 优势 | Docker | 虚拟机 |
|–|–|–|
| 资源利用率更高 | 一台物理机可以运行数百个容器 | 但是一般只能运行数十个虚拟机 |
| 开销更小 | 不需要启动单独的虚拟机占用硬件资源 | 需要启动单独的虚拟机占用硬件资源 |
| 启动速度更快 | 可以在数秒内完成启动 | 需要几十秒甚至数分钟 |

  使用虚拟机是为了更好的实现服务运行环境隔离, 每个虚拟机都有独立的内核,虚拟化可以实现不同操作系统的虚拟机,但是通常一个虚拟机只运行一个服务, 很明显资源利用率比较低且造成不必要的性能损耗, 我们创建虚拟机的目的是为了运行应用程序,比如 Nginx、 PHP、 Tomcat 等 web 程序, 使用虚拟机无疑带来了一些不必要的资源开销,但是容器技术则基于减少中间运行环节带来较大的性能提升。

Docker 的组成

  • Docker 主机(Host): 一个物理机或虚拟机,用于运行 Docker 服务进程和容器。
  • Docker 服务端(Server): Docker 守护进程, 运行 docker 容器。
  • Docker 客户端(Client): 客户端使用 docker 命令或其他工具调用 docker API。
  • Docker 仓库(Registry): 保存镜像的仓库,类似于 git 或 svn 这样的版本控制系统,官方仓库: https://hub.docker.com/
  • Docker 镜像(Images): 镜像可以理解为创建实例使用的模板。
  • Docker 容器(Container): 容器是从镜像生成对外提供服务的一个或一组服务。

详细介绍参见官方文档https://docs.docker.com/engine/docker-overview/

Docker实现需要解决的问题

  容器技术确实有很多优点,不过当使用多个容器时带来的以下问题怎么解决:
  1.怎么样保证每个容器都有不同的文件系统并且能互不影响?
  2.一个 docker 主进程内的各个容器都是其子进程,那么实现同一个主进程下不同类型的子进程? 各个进程间通信能相互访问(内存数据)吗?
  3.每个容器怎么解决 IP 及端口分配的问题?
  4.多个容器的主机名能一样吗?
  5.每个容器都要不要有 root 用户?怎么解决账户重名问题?
  这就不得不引入一个Namespace的概念了。

Namespace

  namespace 是 Linux 系统的底层概念, 在内核层实现,即有一些不同类型的命名空间被部署在核内, 各个 docker 容器运行在同一个 docker 主进程并且共用同一个宿主机系统内核,各 docker 容器运行在宿主机的用户空间, 每个容器都要有类似于虚拟机一样的相互隔离的运行空间, 但是容器技术是在一个进程内实现运行指定服务的运行环境, 并且还可以保护宿主机内核不受其他进程的干扰和影响, 如文件系统空间、网络空间、进程空间等。
| 隔离类型 | 实现功能 | 系统调用参数 | 内核版本 |
|–|–|–|–|
| MNT Namespace(mount) | 提供磁盘挂载点和文件系统的隔离能力 | CLONE_NEWNS | Linux 2.4.19 |
| IPC Namespace(Inter-Process Communication) | 提供进程间通信的隔离能力 | CLONE_NEWIPC | Linux 2.6.19 |
| UTS Namespace(UNIX Timesharing System) | 提供主机名隔离能力 | CLONE_NEWUTS | Linux 2.6.19 |
| PID Namespace(Process Identification) | 提供进程隔离能力 | CLONE_NEWPID | Linux 2.6.24 |
| Net Namespace(network) | 提供网络隔离能力 | CLONE_NEWNET | Linux 2.6.29 |
| User Namespace(user) | 提供用户隔离能力 | CLONE_NEWUSER | Linux 3.8 |

  • MNT Namespace:每个容器都要有独立的根文件系统有独立的用户空间, 以实现在容器里面启动服务并且使用容器的运行环境,容器里面是不能访问宿主机的资源, 宿主机是使用了 chroot 技术把容器锁定到一个指定的运行目录里面。
  • IPC Namespace:一个容器内的进程间通信, 允许一个容器内的不同进程的(内存、 缓存等)数据访问,但是不能跨容器访问其他容器的数据。
  • UTS Namespace:UTS namespace(UNIX Timesharing System 包含了运行内核的名称、版本、底层体系结构类型等信息)用于系统标识, 其中包含了 hostname 和域名domainname , 它使得一个容器拥有属于自己 hostname 标识,这个主机名标识独立于宿主机系统和其上的其他容器。
  • PID Namespace:Linux 系统中,有一个 PID 为 1 的进程(init/systemd)是其他所有进程的父进程, 那么在每个容器内也要有一个父进程来管理其下属的子进程,那么多个容器的进程通 PID namespace 进程隔离(比如 PID 编号重复、 器内的主进程生成与回收子进程等)。
  • Net Namespace:每一个容器都类似于虚拟机一样有自己的网卡、 监听端口、 TCP/IP 协议栈等,Docker 使用 network namespace 启动一个 vethX 接口,这样你的容器将拥有它自己的桥接 ip 地址,通常是 docker0,而 docker0 实质就是 Linux 的虚拟网桥,网桥是在 OSI 七层模型的数据链路层的网络设备,通过 mac 地址对网络进行划分,并且在不同网络直接传递数据。
  • User Namespace:User Namespace 允许在各个宿主机的各个容器空间内创建相同的用户名以及相同的用户 UID 和 GID, 只是会把用户的作用范围限制在每个容器内,即 A 容器和 B 容器可以有相同的用户名称和 ID 的账户,但是此用户的有效范围仅是当前容器内, 不能访问另外一个容器内的文件系统,即相互隔离、互补影响、 永不相见。

  通过这些内核级功能的实现,docker才可以正常工作,实现不同容器间的各种资源的隔离,形成共享同一组硬件及内核上却互不影响的独立应用级虚拟化系统。不过此时,我们还面临一个问题,就是资源使用率的控制。如果一个容器因为BUG或代码本身等原因导致无限制的使用宿主机上的资源,将会导致宿主机CPU或者内存不足进而极有可能影响到其他容器的正常运行。所以我们需要对每个容器的资源利用做一个限制,我们通过内核中的Linux Cgroups功能来限制每个容器能使用的资源的上限。

Linux Cgroups

  Linux Cgroups 的全称是 Linux Control Groups, 它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。此外,还能够对进程进行优先级设置,以及将进程挂起和恢复等操作。
  Cgroups 在内核层默认已经开启,可通过下列命令验证查看Cgroups设置
  CentOS7.6:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost ~]# cat /boot/config-3.10.0-957.el7.x86_64 |grep CGROUP
CONFIG_CGROUPS=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_SCHED=y
CONFIG_BLK_CGROUP=y
# CONFIG_DEBUG_BLK_CGROUP is not set
CONFIG_NETFILTER_XT_MATCH_CGROUP=m
CONFIG_NET_CLS_CGROUP=y
CONFIG_NETPRIO_CGROUP=y
[root@localhost ~]#

  ubuntu1804中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@DockerUbuntu:~# cat /boot/config-4.15.0-70-generic |grep CGROUP
CONFIG_CGROUPS=y
CONFIG_BLK_CGROUP=y
# CONFIG_DEBUG_BLK_CGROUP is not set
CONFIG_CGROUP_WRITEBACK=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_RDMA=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_BPF=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_SOCK_CGROUP_DATA=y
CONFIG_NETFILTER_XT_MATCH_CGROUP=m
CONFIG_NET_CLS_CGROUP=m
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_NET_CLASSID=y
root@DockerUbuntu:~#

  可以看到内核较新的 ubuntu 支持的功能更多。

cgroups 参数具体解释:

1
2
3
4
5
6
7
8
9
10
11
12
blkio:块设备 IO 限制。
cpu:使用调度程序为 cgroup 任务提供 cpu 的访问。
cpuacct:产生 cgroup 任务的 cpu 资源报告。
cpuset:如果是多核心的 cpu,这个子系统会为 cgroup 任务分配单独的 cpu
和内存。
devices:允许或拒绝 cgroup 任务对设备的访问。
freezer:暂停和恢复 cgroup 任务。
memory:设置每个 cgroup 的内存限制以及产生内存资源报告。
net_cls:标记每个网络包以供 cgroup 方便使用。
ns:命名空间子系统。
perf_event:增加了对每 group 的监测跟踪的能力,可以监测属于某个特定的
group 的所有线程以及运行在特定 CPU 上的线程。

  可以通过命令ll /sys/fs/cgroup/查看系统Cgroups

Docker依赖的技术

  Docker容器技术如果真正想在企业中应用,还必须依赖下面的一些技术,会在之后的文章中详细介绍。

容器网络:

  docker 自带的网络 docker network 仅支持管理单机上的容器网络, 当多主机运行的时候需要使用第三方开源网络,例如 calico、 flannel 等。

服务发现:

  容器的动态扩容特性决定了容器 IP 也会随之变化, 因此需要有一种机制可以自动识别并将用户请求动态转发到新创建的容器上, kubernetes 自带服务发现功能,需要结合 kube-dns 服务解析内部域名。

容器监控:

  可以通过原生命令 docker ps/top/stats 查看容器运行状态,另外也可以使
heapster/ Prometheus 等第三方监控工具监控容器的运行状态。

数据管理:

  容器的动态迁移会导致其在不同的 Host 之间迁移,因此如何保证与容器相关的数据也能随之迁移或随时访问,可以使用逻辑卷/存储挂载等方式解决。

日志收集:

  docker 原生的日志查看工具 docker logs, 但是容器内部的日志需要通过 ELK 等专门的日志收集分析和展示工具进行处理。

Docker部署

  我们初步知道了Docker的概念和实现原理,也知道了Docker在使用中可能会遇到的问题,说了这么多,现在我们先将Docker部署一下。
  Docker常见的安装方式有三种,可以通过rpm包下载,或者二进制安装,也可以通过epel源安装。
  官方 rpm 包下载地址:
  https://download.docker.com/linux/centos/7/x86_64/stable/Packages/
  二进制下载地址:
  https://download.docker.com/
https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/
  阿里镜像下载地址:
  https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/
  Ubuntu的安装docker-ce阿里云镜像仓库方式如下(使用 apt-get 进行安装):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# step 1: 安装必要的一些系统工具
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# step 2: 安装GPG证书
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# Step 4: 更新并安装Docker-CE
sudo apt-get -y update
sudo apt-get -y install docker-ce

# 安装指定版本的Docker-CE:
# Step 1: 查找Docker-CE的版本:
# apt-cache madison docker-ce
# docker-ce | 17.03.1~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
# docker-ce | 17.03.0~ce-0~ubuntu-xenial | https://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial/stable amd64 Packages
# Step 2: 安装指定版本的Docker-CE: (VERSION例如上面的17.03.1~ce-0~ubuntu-xenial)
# sudo apt-get -y install docker-ce=[VERSION]

  注意:在与 kubernetes 结合使用的时候,要安装经过 kubernetes 官方测试通过的 docker版本, 避免出现不兼容等未知的及不可预估的问题发生, kubernetes 测试过的docker 版本可以在 github 查询, 具体如下:
https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.14.md#external-dependencies
  安装完成后就可以用systemctl start docker命令启动Docker了。
  然后用docker info命令来验证当前容器信息

1
docker info
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Containers: 0 #当前主机运行的容器总数
Running: 0 #有几个容器是正在运行的
Paused: 0 #有几个容器是暂停的
Stopped: 0 #有几个容器是停止的
Images: 0 #当前服务器的镜像数
Server Version: 18.09.9 #服务端版本
Storage Driver: overlay2 #正在使用的存储引擎
Backing Filesystem: extfs #后端文件系统,即服务器的磁盘文件系统
Supports d_type: true #是否支持 d_type
Native Overlay Diff: true #是否支持差异数据存储
Logging Driver: json-file #日志类型
Cgroup Driver: cgroupfs #Cgroups 类型
Plugins: #插件
Volume: local #卷
Network: bridge host macvlan null overlay # overlay 夸主机通信
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog #日
志类型
Swarm: inactive #是否支持 swarm
Runtimes: runc #已安装的容器运行时
Default Runtime: runc #默认使用的容器运行时
Init Binary: docker-init #初始化容器的守护进程,即 pid 为 1 的进程
containerd version: 894b81a4b802e4eb2a91d1ce216b8817763c29fb #版本
runc version: 425e105d5a03fabd737a126ad93d62a9eeede87f # runc 版本
init version: fec3683 #init 版本
Security Options: #安全选项
Apparmor #安全模块, https://docs.docker.com/engine/security/apparmor/
seccomp #审计(操作), https://docs.docker.com/engine/security/seccomp/
Profile: default #默认的配置文件
Kernel Version: 4.15.0-55-generic #宿主机内核版本
Operating System: Ubuntu 18.04.3 LTS #宿主机操作系统
OSType: linux #宿主机操作系统类型
Architecture: x86_64 #宿主机架构
CPUs: 2 #宿主机 CPU 数量
Total Memory: 3.83GiB #宿主机总内存
Name: DockerUbuntu #宿主机 hostname
ID: ZFPD:UIA5:SR6E:Y6SS:52QL:5MPT:VDY3:ATVI:QMVG:HAFF:MN74:2HPD #宿主机
ID
Docker Root Dir: /var/lib/docker #宿主机数据保存目录
Debug Mode (client): false #client 端是否开启 debug
Debug Mode (server): false #server 端是否开启 debug
Registry: https://index.docker.io/v1/ #镜像仓库
Labels: #其他标签
Experimental: false #是否测试版
Insecure Registries: #非安全的镜像仓库
127.0.0.0/8
Live Restore Enabled: false #是否开启活动重启(重启 docker-daemon 不关闭容器)
Product License: Community Engine #产品许可信息

在结尾可能会有类似warning警报,

1
WARNING: No swap limit support

这是提示说我们没有开启 swap 资源限制,这就需要我们通过修改内核参数,来限制swap资源的使用。

1
vim /etc/default/grub
1
GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0 cgroup_enable=memory swapaccount=1"
1
2
update-grub
reboot

  重启生效。至此,docker工具部署就完成了~


一个低调的男人