docker在前端的使用 - 入门篇

120 阅读12分钟
  1. docker在前端的使用 - 入门篇
  2. docker在前端的使用 - 实践篇
  3. docker在前端的使用 - 命令篇

这是docker在前端的使用实践篇,其他可按需阅读。

在招聘后端同学的JD中,docker是一个比较重要的考察点,但是这些年前端的发展使得前端coder也需要懂一些docker的基本使用,这样在部署的时候才不至于过渡依赖运维同学。

因为这是针对前端coder的一篇入门级的docker介绍,所以我们就用理论+实践的方法来了解一下。

虚拟化

首先先了解一下什么是虚拟化,简单来说,虚拟化是一些程序创建虚拟化的过程,虽然说虚拟化可以应用到计算机、操作系统、网络、存储设备等,但是服务器才是虚拟化应用的最主要的地方。

虚拟化使用软件来模拟硬件并创建虚拟计算机系统,这样一来就可以在单台服务器上运行多个虚拟系统,也就是运行多个操作系统和应用,从来提升效益。

比如,我前段时间比较想玩「蓝警」和「红警」,于是就在mac上搭了一个windows虚拟机,用来对付7个冷酷,地图上还是北极圈好玩,上来第一步还是先要炸桥的,要不打不过。资本主义国家,间谍偷钱,搞个engineer给自己升个星什么的基本操作还是要安排上的。。。。。当然这个操作肯定要在下班之后。

扯的有点远了,我们接着说这个磁爆步兵,哦不,是接着说这个虚拟化的事

没有虚拟化之前,一个服务器上往往只能运行一个系统,由于应用的运行环境不用,所以一个操作系统上只能运行一类应用,(所以我们下载软件安装包的时候会让我们选择 Windows、macOS、Linux等不同的版本)。即使是小型数据中心也必须部署大量服务器,而每台服务器的容量利用率只有 5% 到 15%,无论以哪种标准来衡量,都十分的低效。

为什么要进行虚拟化

就我们上面的说了,由于系统间的不同,一个操作系统只运行一个应用,导致的结果就是,一个小型的数据中心,也需要部署大量的服务器,而每个服务器的使用率往往只有4%~5%,随着服务器的性能不断提升,这个数值可能变的更小。

使用虚拟化主要是为了提升服务器的利用率,(也可能是为了满足我们自己的私欲,闪电风暴即将来袭)用来解决高性能的物理硬件产能过剩和老的旧的硬件产能过低的重组重用,透明化底层物理硬件,从而最大化利用物理硬件。

随着技术的不断发展,虚拟化技术还具有容灾,方便管理维护服务器的好处。

Docker

Docker是目前虚拟化技术用的最多的工具,相信大家都有所耳闻,没有听过的就好好读几遍文章。

鲸鱼是操作系统。

要交付的应用程序是各种货物,要将各种形状和尺寸不同的货物放到大鲸鱼上,得考虑每件货物怎么安放(应用程序配套的环境),还得考虑货物和货物之间能否重叠起来(应用程序依赖的环境是否会冲突)。

Docker和VM最大的区别就在于,部署在Docker下的机器是不需要多个操作系统的,虚拟机一旦被开启,预分配给它的资源将全部被占用。

现在使用了集装箱(容器)把每件货物都放到集装箱里,这样大鲸鱼可以用同样地方式安放、堆叠集装了,省事省力。

Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的Linux容器解决方案。而Linux容器是Linux发展出了另一种虚拟化技术,简单来讲,Linux容器不是模拟一个完整的操作系统,而是对进程进行隔离,相当于是在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。

Docker的使用场景

相信大家都遇到过这样的一件事,就是本地开发的好好的,发到线上挂了;我的mac电脑上运行的五彩斑斓,别人的windows电脑上一片空白;我电脑上运行的好好的,在qa的电脑上挂了,然后被记了一个bug。

相信我,这不怪你。要怪就怪这系统无情,环境不公。

一款产品从开发到上线,从操作系统、运行环境再到应用配置,开发和运维之前的协作需要关心很多东西,这也是互联网公司不得不面对的一个问题,特别是各种版本的迭代,不同环境的兼容、迁移,对运维人员都是考验。

这么头疼的问题得解决啊,有需求就会有产品,Docker就是这样的一个标准化的解决方案。

Docker解决了环境配置的麻烦,不存在换台机器就要重装一次,费事费力。从根本上解决问题,软件可以带着环境安装,我们常见的是环境带着软件,而docker是软件带着环境。

安装的时候,把原始环境一模一样地复制过来。开发人员利用Docker可以消除协作编码时的问题。“本地过了就是过了,服务器上没过说明服务器有问题”

打一个比方,集装箱(容器)对于远洋运输(应用运行)来说十分重要。集装箱(容器)能保护货物(应用),让其不会相互碰撞(应用冲突)而损坏,也能保障当一些危险货物发生规模不大的爆炸(应用崩溃)时不会波及其它货物(应用)但是把货物(应用)装载在集装箱(容器)中并不是一件简单的事情。而出色的码头工人(Docker)的出现解决了这一问题。它(Docker)使得货物装载到集装箱(容器)这一过程变得轻而易举。对于远洋运输(应用运行)而言,用多艘小货轮(虚拟机)代替原来的大货轮(实体机)也能保证货物(应用)彼此之间的安全,但是和集装箱(容器)比,成本过高,但适合运输某些重要货物(应用)。

Docker的优势

  1. Docker有着比虚拟机更少的抽象层,由于Docker不需要Hypervisor实现硬件资源虚拟化,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源,因此在Cpu、内存利用率上Docker将会在效率上有明显优势。

  2. Docker利用的是宿主机的内核,而不需要Guest OS,因此,当新建一个容器时,Docker不需要和虚拟机一样重新加载一个操作系统,避免了引导、加载操作系统内核这个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载Guest OS,这个新建过程是分钟级别的,而Docker由于直接利用宿主机的操作系统则省略了这个过程,因此新建一个Docker容器只需要几秒钟。 我们拿常用的VM来对比一下

对比项DockerVM
启动速度秒级几分钟
硬盘使用MB级别GB级别
容器/虚拟机部署量成百上千个几十个不能再多了
性能接近原生慢于原生
移植性轻便、灵活、适用于Linux笨重、与虚拟化技术耦合度高
隔离性进程级系统级
安全性较弱较强
硬件亲和性面向软件开发者面向硬件运维者

Docker的核心

Docker技术的三大核心概念,分别是:

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

举个例子,这个三个的关系就好比 我玩红警的游戏,你也想玩,因为红警不需要安装,我直接把红警这个文件夹拷贝给你就可以直接玩,那我首先要把这些文件打一个压缩包,这个压缩包你可以理解为镜像(Image),而我需要用U盘拷贝给你,我的这个U盘中可不止有红警的压缩包,也有蓝警的,也有CS的,也有拳皇的。我的这个U盘可以理解为仓库(Repository)。你在你的电脑上解压了开始玩,隔壁小明也想玩就也拷贝了一份,你打冷酷玩家和小明打简单玩家没有什么关系,互不干扰,这个时候,你的电脑和小明的电脑可以理解为容器(Container)

镜像是Docker运行容器的前提,仓库是存放镜像的场所,可见镜像是Docker的核心。

镜像

Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数,使用镜像可以创建出容器

我们在开发机上设计,编写程序,打包封装成镜像。而镜像就好比一个房屋的图纸,通过同一个图纸造出来的房屋(容器)都是一模一样的。

Docker镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序,库,资源,配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷,环境变量,用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

镜像(Image)就是一堆只读层(read-only layer)的统一视角

多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker内部的实现细节,并且能够在主机的文件系统上访问到。统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式。

容器

Container容器的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。

容器是运行程序的地方,不同容器之间相互隔离,互不影响。

通过镜像造出来的容器与原来的镜像将没有直接的联系关系。正如一个建好的屋子,是谁入住,添加了什么家具,都和图纸无关。

容器和镜像几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的,毕竟我们的一切操作是要在容器中进行的。

仓库

就像GitHub上存放代码一样,仓库是用来存放Docker镜像的地方,大家可以把自己好的镜像共享出来,互相交换使用。

总的来说,就是仓库中存放着镜像,而镜像可以生成容器,容器存放着程序运行的资源,环境等数据。所有的容器共享着一个操作系统(1台机器)。

Docker的隔离性

Namespaces

命名空间(namespaces)是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。在日常使用 Linux 或者 macOS 时,我们并没有运行多个完全分离的服务器的需要, 但是如果我们在服务器上启动了多个服务,这些服务其实会相互影响的,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,这是很多时候我们都不愿意看到的, 我们更希望运行在同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样。

在这种情况下,一旦服务器上的某一个服务被入侵,那么入侵者就能够访问当前机器上的所有服务和文件,这也是我们不想看到的,而 Docker 其实就通过 Linux 的 Namespaces 对不同的容器实现了隔离。

Linux 的命名空间机制提供了以下七种不同的命名空间,包括 CLONE_NEWCGROUP、CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER 和 CLONE_NEWUTS,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。

进程

进程是 Linux 以及现在操作系统中非常重要的概念,它表示一个正在执行的程序,也是在现代分时系统中的一个任务单元。在每一个 *nix 的操作系统上,我们都能够通过 ps 命令打印出当前操作系统中正在执行的进程,比如在 Ubuntu 上,使用该命令就能得到以下的结果:

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Apr08 ?        00:00:09 /sbin/init
root         2     0  0 Apr08 ?        00:00:00 [kthreadd]
root         3     2  0 Apr08 ?        00:00:05 [ksoftirqd/0]
root         5     2  0 Apr08 ?        00:00:00 [kworker/0:0H]
root         7     2  0 Apr08 ?        00:07:10 [rcu_sched]
root        39     2  0 Apr08 ?        00:00:00 [migration/0]
root        40     2  0 Apr08 ?        00:01:54 [watchdog/0]

当前机器上有很多的进程正在执行,在上述进程中有两个非常特殊,一个是 pid 为 1 的 /sbin/init 进程,另一个是 pid 为 2 的 kthreadd 进程,这两个进程都是被 Linux 中的上帝进程 idle 创建出来的,其中前者负责执行内核的一部分初始化工作和系统配置,也会创建一些类似 getty 的注册进程,而后者负责管理和调度其他的内核进程。

如果我们在当前的 Linux 操作系统下运行一个新的 Docker 容器,并通过 exec 进入其内部的 bash 并打印其中的全部进程,我们会得到以下的结果:

root@iZ255w13cy6Z:~# docker run -it -d ubuntu
b809a2eb3630e64c581561b08ac46154878ff1c61c6519848b4a29d412215e79
root@iZ255w13cy6Z:~# docker exec -it b809a2eb3630 /bin/bash
root@b809a2eb3630:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 15:42 pts/0    00:00:00 /bin/bash
root         9     0  0 15:42 pts/1    00:00:00 /bin/bash
root        17     9  0 15:43 pts/1    00:00:00 ps -ef

在新的容器内部执行 ps 命令打印出了非常干净的进程列表,只有包含当前 ps -ef 在内的三个进程,在宿主机器上的几十个进程都已经消失不见了。说明我们的容器是和外部完全隔离的。

网络

如果 Docker 的容器通过 Linux 的命名空间完成了与宿主机进程的网络隔离,但是却有没有办法通过宿主机的网络与整个互联网相连,就会产生很多限制,所以 Docker 虽然可以通过命名空间创建一个隔离的网络环境,但是 Docker 中的服务仍然需要与外界相连才能发挥作用。

每一个使用 docker run 启动的容器其实都具有单独的网络命名空间,Docker 为我们提供了四种不同的网络模式,Host、Container、None 和 Bridge 模式。

Docker 默认的网络设置模式:网桥模式。在这种模式下,除了分配隔离的网络命名空间之外,Docker 还会为所有的容器设置 IP 地址。当 Docker 服务器在主机上启动之后会创建新的虚拟网桥 docker0,随后在该主机上启动的全部服务在默认情况下都与该网桥相连。

在默认情况下,每一个容器在创建时都会创建一对虚拟网卡,两个虚拟网卡组成了数据的通道,其中一个会放在创建的容器中,会加入到名为 docker0 网桥中。我们可以使用如下的命令来查看当前网桥的接口:

$ brctl show
bridge name bridge id       STP enabled interfaces
docker0     8000.0242a6654980   no      veth3e84d4f
                                    veth9953b75

总结

Docker 为前端应用提供了一致的运行环境,带来了快速部署、回滚等特性。除此之外,还需要考虑容器化应用的部署,容器的编排、调度等问题,可以使用 Kubernetes 等容器管理平台。