从单机容器化技术 Docker 到分布式容器化架构方案 Kubernetes,当今容器化技术发展盛行。本文面向小白读者,旨在快速带领读者了解 Docker、Kubernetes 的架构、原理、组件及相关使用场景。
Docker
1.什么是 Docker
Docker 是一个开源的应用容器引擎,是一种资源虚拟化技术,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上。虚拟化技术演历路径可分为三个时代:
- 物理机时代,多个应用程序可能跑在一台物理机器上
- 虚拟机时代,一台物理机器启动多个虚拟机实例,一个虚拟机跑多个应用程序
- 容器化时代,一台物理机上启动多个容器实例,一个容器跑多个应用程序
在没有 Docker 的时代,我们会使用硬件虚拟化(虚拟机)以提供隔离。这里,虚拟机通过在操作系统上建立了一个中间虚拟软件层 Hypervisor ,并利用物理机器的资源虚拟出多个虚拟硬件环境来共享宿主机的资源,其中的应用运行在虚拟机内核上。但是,虚拟机对硬件的利用率存在瓶颈,因为虚拟机很难根据当前业务量动态调整其占用的硬件资源,加之容器化技术蓬勃发展使其得以流行。
Docker、虚拟机对比:
特性 | Docker | 虚拟机 |
---|
另外开发人员在实际的工作中,经常会遇到测试环境或生产环境与本地开发环境不一致的问题,轻则修复保持环境一致,重则可能需要返工。但 Docker 恰好解决了这一问题,它将软件程序和运行的基础环境分开。开发人员编码完成后将程序整合环境通过 DockerFile 打包到一个容器镜像中,从根本上解决了环境不一致的问题。
2.Docker 的构成
Docker 由镜像、镜像仓库、容器三个部分组成
- 镜像: 跨平台、可移植的程序+环境包
- 镜像仓库: 镜像的存储位置,有云端仓库和本地仓库之分,官方镜像仓库地址(https://hub.docker.com/)
- 容器: 进行了资源隔离的镜像运行时环境
3.Docker 的实现原理
到此读者们肯定很好奇 Docker 是如何进行资源虚拟化的,并且如何实现资源隔离的,其核心技术原理主要有(内容部分参考自 Docker 核心技术与实现原理):
(1).Namespace
在日常使用 Linux 或者 macOS 时,我们并没有运行多个完全分离的服务器的需要,但是如果我们在服务器上启动了多个服务,这些服务其实会相互影响的,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,这是很多时候我们都不愿意看到的,我们更希望运行在同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样。
资料领取直通车:Golang云原生最新资料+视频学习路线https://docs.qq.com/doc/DTllySENWZWljdWp4
Go语言学习地址:Golang DevOps项目实战https://ke.qq.com/course/422970?flowToken=1043212
命名空间 (Namespaces) 是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。Linux 的命名空间机制提供了以下七种不同的命名空间,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。
- CLONE_NEWCGROUP
- CLONE_NEWIPC
- CLONE_NEWNET
- CLONE_NEWNS
- CLONE_NEWPID
- CLONE_NEWUSER
- CLONE_NEWUTS
在 Linux 系统中,有两个特殊的进程,一个是 pid 为 1 的 /sbin/init 进程,另一个是 pid 为 2 的 kthreadd 进程,这两个进程都是被 Linux 中的上帝进程 idle 创建出来的,其中前者负责执行内核的一部分初始化工作和系统配置,也会创建一些类似 getty 的注册进程,而后者负责管理和调度其他的内核进程。
当在宿主机运行 Docker,通过docker run
或docker start
创建新容器进程时,会传入 CLONE_NEWPID 实现进程上的隔离。
接着,在方法createSpec
的setNamespaces
中,完成除进程命名空间之外与用户、网络、IPC 以及 UTS 相关的命名空间的设置。
func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
s := oci.DefaultSpec()
// ...
if err := setNamespaces(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux spec namespaces: %v", err)
}
return &s, nil
}
func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {
// user
// network
// ipc
// uts
// pid
if c.HostConfig.PidMode.IsContainer() {
ns := specs.LinuxNamespace{Type: "pid"}
pc, err := daemon.getPidContainer(c)
if err != nil {
return err
}
ns.Path = fmt.Sprintf("/proc/%d/ns/pid", pc.State.GetPID())
setNamespace(s, ns)
} else if c.HostConfig.PidMode.IsHost() {
oci.RemoveNamespace(s, specs.LinuxNamespaceType("pid"))
} else {
ns := specs.LinuxNamespace{Type: "pid"}
setNamespace(s, ns)
}
return nil
}
网络
当 Docker 容器完成命名空间的设置,其网络也变成了独立的命名空间,与宿主机的网络互联便产生了限制,这就导致外部很难访问到容器内的应用程序服务。Docker 提供了 4 种网络模式,通过--net
指定。
- host
- container
- none
- bridge