JavaScript 多文件下载

3.iframe方式 兼容多种浏览器

function IEdownloadFile(fileName, contentOrPath){
var ifr = document.createElement(‘iframe’);
ifr.style.display = ‘none’;
ifr.src = contentOrPath;
document.body.appendChild(ifr);
// 保存页面 -> 保存文件
ifr.contentWindow.document.execCommand(‘SaveAs’, false, fileName);
document.body.removeChild(ifr);
}

//图片类型保存

var isImg = contentOrPath.slice(0, 10) ===
“data:image”

// dataURL 的情况

isImg && ifr.contentWindow.document.write(
“<span style=”
font-family: Arial, Verdana, sans-serif;
“>”
);</span>

1.h5方式 模拟点击 不支持ie

function downloadFile(fileName, content){
var aLink = document.createElement(‘a’);
, blob = new Blob([content])
, evt = document.createEvent(“HTMLEvents”);

evt.initEvent(“click”);

aLink.download = fileName;
aLink.href = URL.createObjectURL(blob);
aLink.dispatchEvent(evt);
}
2.打开新窗口 模拟保存 不支持360 搜狗等安全浏览器

// 将文件在一个 window 窗口中打开,并隐藏这个窗口。
var win = window.open(“path/to/file.ext”, “new Window”, “width=0,height=0”);
// 在 win 窗口中按下 ctrl+s 保存窗口内容
win.document.execCommand(“SaveAs”, true, “filename.ext”);
// 使用完了,关闭窗口
win.close();

 

阿里云OSS文件下载功能简易实现

1.场景说明
将阿里云OSS文件下载至本地。

2.操作方法
提供2中文件下载方式:1、原生的输入输出流处理;2、commons-io-2.4-sources.jar包中的FileUtils.copyURLToFile现成方法处理。

2.1方式一
利用原生的输入输出流处理。

*步:获取OSS文件链接,读取输入流

// 截取片段代码
URL url = new URL(urllink);
//打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
InputStream in = url.openStream();
String gaokuai_url = HttpUpload.uploadOSS(in, fileName);

// logger.info(“FeedbackServiceImpl.downloadFilesFromUrl gaokuai_url:” + gaokuai_url);
if(!gaokuai_url.equals(“”)){
if(sb.length() >0){
sb.append(tag + gaokuai_url);
}else{
sb.append(gaokuai_url);
}
}

第二步:文件下载

public static void download(InputStream inputStream){

try {
File file = new File(“/Users/loongshawn/Downloads/Penguins.jpg”);
OutputStream outputStream = new FileOutputStream(file);

int byteCount = 0;
//1M逐个读取
byte[] bytes = new byte[1024*1024];
while ((byteCount = inputStream.read(bytes)) != -1){
outputStream.write(bytes, 0, byteCount);
}

inputStream.close();
outputStream.close();
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}

2.2方式二
利用到了commons-io-2.4-sources.jar包中的FileUtils.copyURLToFile方法。

public static void method2(String urllink){

try {
URL httpurl = new URL(urllink);
File file = new File(“/Users/loongshawn/Downloads/Penguins.jpg”);
FileUtils.copyURLToFile(httpurl, file);
} catch (MalformedURLException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}

3.效果展示
测试demo如下,提供了两种文件下载的方式,上述两种方式均测试可用,但有一点需要说明,OSS文件有访问权限设置,本例中的OSS没有设置访问限制。

public static void main(String[] args){

System.out.println(“HelloWorld!”);

String urllink = “http://XXXX/attachment/201704/06/20170406163542/20170406Penguins.jpg”;

method1(urllink);
method2(urllink);
}

public static void method1(String urllink){

try {
URL url = new URL(urllink);
//打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
InputStream in = url.openStream();
download(in);

} catch (IOException e){

}
}

public static void method2(String urllink){

try {
URL httpurl = new URL(urllink);
File file = new File(“/Users/loongshawn/Downloads/Penguins.jpg”);
FileUtils.copyURLToFile(httpurl, file);
} catch (MalformedURLException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}

}

下载结果:

%title插图%num

从阿里云上下载文件

@RequestMapping(value = “downloadDatumInfo”, method = {RequestMethod.POST})
public void downloadDatumInfo(@RequestParam(value=”parmData”) String parmData,HttpServletRequest request, HttpServletResponse response) throws IOException{
Map<String, Object> map = CommonUtil.jsonToObject(parmData);
request.setCharacterEncoding(“utf-8”);
response.setCharacterEncoding(“utf-8”);
String fileName = (String) map.get(“fileName”);
String filePath = (String) map.get(“filePath”);
response.setHeader(“Content-Disposition”, “attachment;filename=” + URLEncoder.encode(fileName,”utf-8″));
String companyCode = filePath.substring(42, 45);
String diskName = “yunsuo”+companyCode+”/”;
String key = filePath.substring(filePath.lastIndexOf(“/”)+1,filePath.length());
OSSClient ossClient = new OSSClient(aliyunOssConfig.getEndPoint(), aliyunOssConfig.getAccessKeyId(), aliyunOssConfig.getAccessKeySecret());
InputStream in = OSSUnit.getOSS2InputStream(ossClient, “yunsuo”, diskName, key);
byte[] buff = new byte[1024];
BufferedInputStream bis = null;
OutputStream os = null;
try {
os = response.getOutputStream();
bis =new BufferedInputStream(in);
int len = 0;
while ((len = in.read(buff)) != -1) {
os.write(buff, 0, len);
}
os.flush();
os.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 根据key获取OSS服务器上的文件输入流
*
* @param client
* OSS客户端
* @param bucketName
* bucket名称
* @param diskName
* 文件路径
* @param key
* Bucket下的文件的路径名+文件名
*/
public static InputStream getOSS2InputStream(OSSClient client, String bucketName, String diskName, String key) {
OSSObject ossObj = client.getObject(bucketName, diskName + key);
return ossObj.getObjectContent();
}

除了 Docker,我们还有哪些选择?

所谓三十年河东,三十年河西,曾经在容器领域叱咤风云的 Docker 如今已风光不再。抛开情怀,我们不得不承认,Docker 已经被后浪拍死在沙滩上了……

大约 4 年前的容器领域,Docker 是唯一的选择。

然而,如今情况已然大不同,Docker 不再是是唯一的选择,它只不过是一个容器引擎而已。我们可以用 Docker 构建、运行、拉取、推送或检查容器镜像,但是这里的每一项任务,都可以用其他工具替代,甚至有些工具比 Docker 还好。

所以,下面就让我们来探索一下这个领域,然后卸载和忘记 Docker 吧。

%title插图%num

为什么说不要用 Docker 了?

如果长期以来你一直在使用 Docker,那么说服你考虑其他工具可能需要多费点唇舌。

首先,Docker 是一个整体化的工具,它试图做好所有的事情,但往往只会适得其反。在大多数情况下,我们应该选择专门的工具,它可能只做一件事情,但会做到*好。

可能你因为担心需要学习使用不同的 CLI、不同的 API 或接受不同的概念,所以会害怕使用其他工具。但是,请不用担心。本文介绍的任何工具都可以完美地无缝衔接,因为它们(包括 Docker)都遵循同一个 OCI(OpenContainer Initiative,开放容器计划)规范。OCI 包括容器运行时、容器分发和容器镜像的规范,涵盖了使用容器所需的所有功能。

因为有了 OCI,所以你可以自由选择适合自己的需求的工具,与此同时,你可以继续使用与 Docker 相同的 API 和 CLI 命令。

因此,如果你愿意尝试新工具,那么我们就来比较一下 Docker 与其竞争对手的优缺点和功能,看看是否有必要考虑放弃 Docker,并尝试使用一些新鲜出炉的工具。

%title插图%num

容器引擎

 

在比较 Docker 与其他工具时,我们需要分别讨论它的各个组件,首先要讨论的就是容器引擎。

容器引擎是一种工具,它提供了处理镜像与容器的用户界面,这样你就不需要与 SECCOMP 规则或 SELinux 策略苦苦纠缠了。除此之外,容器引擎还可以从远程仓库提取镜像,并将其解压到本地磁盘上。它似乎也运行容器,但是实际上,它的工作是创建容器清单以及镜像层的目录。接着,它将这些文件传递给 runc 或 crun 等容器运行时。

目前有很多容器引擎可供我们使用,不过 Docker *主要的竞争对手是红帽开发的 Podman。与 Docker 不同,Podman 不需要运行守护进程,也不需要 root 特权,这些都是 Docker 长期以来一直备受关注的问题。从名字就可以看出来,Podman 不仅可以运行容器,还可以运行 pod。

如果你不熟悉 pod 的话,我可以简单介绍一下:pod 是 Kubernetes 的*小计算单元,由一个或多个容器 (主容器与负责支持主容器的 sidercar 容器) 组成。因此,Podman 用户以后可以很轻松地将他们的工作负载迁移到 Kubernetes。

下面,我们通过一个简单的演示来说明如何在一个 Pod 中运行两个容器:

~ $ podman pod create --name mypod~ $ podman pod listPOD ID        NAME    STATUS    CREATED         # OF CONTAINERS   INFRA ID211eaecd307b  mypod   Running   2 minutes ago   1                 a901868616a5 ~ $ podman run -d --pod mypod nginx  # First container~ $ podman run -d --pod mypod nginx  # Second container~ $ podman ps -a --pod CONTAINER ID IMAGE                          COMMAND               CREATED        STATUS            PORTS  NAMES               POD           POD NAME3b27d9eaa35c  docker.io/library/nginx:latest  nginx -g daemon o...  2 seconds ago Up 1 second ago         brave_ritchie      211eaecd307b  mypodd638ac011412 docker.io/library/nginx:latest nginx -g daemon o...  5 minutesago  Up 5 minutes ago         cool_albattani      211eaecd307b mypoda901868616a5 k8s.gcr.io/pause:3.2                                  6 minutesago  Up 5 minutes ago         211eaecd307b-infra  211eaecd307b mypod

*后一点,Podman 提供的 CLI 命令与 Docker 完全相同,因此你只需执行

alias docker=podman

然后就像什么都没有发生过一样。

除了 Docker 和 Podman 之外,还有其他容器引擎,但我并不看好它们的发展,或者不适合用于本地开发。

不过,如果你想对容器引擎有一个较为完整的了解,我也可以介绍一些:

  • LXD:LXD 是 LXC(Linux 容器)的容器管理器(守护进序)。这个工具提供了运行系统容器的能力,而这些系统容器提供了类似于虚拟机的容器环境。该工具比较小众,没有太多用户,所以除非你有非常特殊的用例,否则*好还是使用 Docker 或 Podman。
  • CRI-O:如果在网上搜索 cri-o 是什么,你可能会发现它被描述成了一种容器引擎。但实际上,它是一种容器运行时。它既不是容器引擎,也不适合“常规”使用。我的意思是说,它是专门作为 Kubernetes 运行时(CRI)而创建的,并不是给*终用户使用的。
  • rkt:rkt(读作“rocket”)是 CoreOS 开发的容器引擎。这里提到这个项目只是为了清单的完整性,因为这个项目已经结束了,它的开发也停止了,因此你不应该再使用它。

%title插图%num

构建镜像

 

对于容器引擎,实际上 Docker 的替代品只有一种选择(即 Podman)。但是,在构建镜像方面,我们有很多选择。

首先,我们来看一看 Buildah。这也是一款红帽开发的工具,可以很好地与 Podman 协同工作。如果你已经安装了 Podman,可能会注意到 podman build 子命令,因为它的二进制文件已经包含在 Podman 中了,实际上这个命令只是经过包装的 Buildah。

至于功能,Buildah 沿用了 Podman 的方针:没有守护进程,不需要 root 特权,而且生成的是符合 OCI 的镜像,因此你的镜像的运行方式与使用 Docker 构建的镜像完全相同。它还能使用 Dockerfile 或 Containerfile 构建镜像, Dockerfile 与 Containerfile 实际上是同一个东西,只是叫法不同罢了。除此之外,Buildah 还提供了对镜像层更精细的控制,支持提交大量的变更到单个层。我认为,它与 Docker 之间有一个出乎意料的区别(但这个区别是好事),那就是使用 Buildah 构建的镜像特定于用户,因此你可以只列出自己构建的镜像。

你可能会问,既然 Podman CLI 中已经包含了 Buildah,为什么还要使用单独的 Buildah CLI 呢?其实,Buildah CLI 是 podman build 所包含的命令的超集,因此你可能不需要直接使用 BuildahCLI,但是通过使用它,你可能会发现一些额外的功能。

下面,我们来看一个示例:

~ $ buildah bud -f Dockerfile . ~ $ buildah from alpine:latest  # Create starting container - equivalent to"FROM alpine:latest"Getting image source signaturesCopying blob df20fa9351a1 doneCopying config a24bb40132 doneWriting manifest to image destinationStoring signaturesalpine-working-container # Name of the temporary container~ $ buildah run alpine-working-container -- apk add--update --no-cache python3  # equivalentto "RUN apk add --update --no-cache python3"fetchhttp://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gzfetchhttp://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz... ~ $ buildah commit alpine-working-containermy-final-image  # Create final imageGetting image source signaturesCopying blob 50644c29ef5a skipped: already existsCopying blob 362b9ae56246 doneCopying config 1ff90ec2e2 doneWriting manifest to image destinationStoring signatures1ff90ec2e26e7c0a6b45b2c62901956d0eda138fa6093d8cbb29a88f6b95124c ~ # buildah imagesREPOSITORY              TAG     IMAGE ID      CREATED         SIZElocalhost/my-final-image latest  1ff90ec2e26e 22 seconds ago  51.4 MB

从上面的脚本可以看出,你可以直接使用 buildah bud 构建镜像,其中 bud 代表使用 Dockerfile 进行构建,你也可以使用其他脚本化的方法,比如使用 Buildahs 的 from、run 和 copy,它们分别对应 Dockerfile 中的 FROM、RUN、COPY 命令。

接下来是 Google 的 Kaniko。Kaniko 也是利用 Dockerfile 构建容器镜像,而且与 Buildah 类似,它也不需要守护进程。但它与 Buildah 的主要区别在于,Kaniko 更加侧重于 Kubernetes 中的镜像构建。

Kaniko 本身也要作为镜像(gcr.io/kaniko-project/executor) 运行,这对于Kubernetes 来说是没有问题的,但对于本地构建来说不是很方便,并且在某种程度上违背了构建镜像的目的,因为你需要使用 Docker 运行 Kaniko 镜像才能构建镜像。话虽如此,如果你正在寻找在 Kubernetes 集群中构建镜像的工具 (例如在 CI/CD 管道中),那么 Kaniko 可能是一个不错的选择,因为它不需要守护进程,而且更安全。

以我个人的经验来看,我认为两者都能很好地完成工作,但是使用 Kaniko 时,我遇到了一些随机的构建故障,而且在将镜像推送到仓库时也出现了失败的情况。

我要介绍的第三个工具是 buildkit,也可以称之为 docker build 二代。它是 Moby 项目的一部分(与 Docker一样),只需设置 DOCKER_BUILDKIT=1 docker build,就可以启动这个工具,并作为 Docker 的一个实验性功能使用。那么,这个工具究竟能给你带来什么?它带来了很多改进和很酷的功能,包括并行构建、跳过未使用的阶段、更好的增量构建以及不需要 root 权限等构建。但是,它仍然需要运行守护进程 (buildkitd)。因此,如果你不想摆脱 Docker,同时又想要一些新的功能和改进,那么可以考虑一下 buildkit。

这里,我也会列出一些其他的工具,它们有各自的特定用途,但不是我的首选:

  • Source-To-Image(S2I):这是一个不使用 Dockerfile,直接根据源代码构建镜像的工具包。这个工具在简单的预期场景和工作流中表现良好,但如果你需要多一些自定义,如果你的项目的结构不符合预期,那么它就变得非常烦人和笨拙。如果你对 Docker 不太满意,或者你在 OpenShift 集群上构建镜像,则可以考虑使用 S2I,因为使用 S2I 构建镜像是它的一个内置功能。
  • Jib:这是一款由 Google 开发的工具,专门用于构建 Java 镜像。它提供了 Maven 和 Gradle 插件,可以让你轻松地构建镜像,而无需在意 Dockerfile。
  • Bazel:这也是一款由 Google 开发的工具。它不仅可用于构建容器镜像,而且是一个完整的构建系统。如果你只是想构建镜像,那么使用 Bazel 可能会有点大材小用,但*对是一种不错的学习体验,如果你愿意,可以先从 rules_docker 着手。

%title插图%num

容器运行时

 

*后我们来说说负责运行容器的容器运行时。容器运行时是整个容器生命周期的一部分,除非你对速度、安全性等有一些非常特殊的要求,否则请不要乱动它。

看到这里,如果你感到厌倦了,则可以跳过这一部分。但是,如果你想了解一下在容器运行时方面,都有哪些选择,则可以看看下面这些:

runc 是一款流行的容器运行时,且符合 OCI 容器运行时规范。Docker(通过containerd)、Podman 和 CRI-O 都在使用它,因此无需我多言。它几乎是所有容器引擎的默认设置,因此即便你在阅读本文后抛弃了 Docker,很可能仍然会使用 runc。

runc 的另一种替代品是 crun。这是一款由红帽开发的工具,全部用 C 语言编写(runc 是用 Go 编写的),所以它比 runc 更快,内存效率更高。由于它也是兼容 OCI 的运行时,所以如果你想试试看的话,应该能很快上手。虽然它现在还不是很流行,但是它即将作为 RHEL 8.3 版本的备选 OCI 运行时,出现在技术预览中,而且考虑到它是红帽的产品,所以*终很可能会成为 Podman 或 CRI-O 的默认配置。

说到 CRI-O,前面我说过,它并不是容器引擎,而是容器运行时。这是因为 CRI-O 没有推送镜像之类的功能,但这些功能是容器引擎应该具备的。CRI-O 内部使用 runc 来运行容器。你不应该在自己的机器上尝试使用这个运行时,因为它的设计就是 Kubernetes 节点上的运行时,而且它是“Kubernetes 所需的唯一的运行时”。因此,除非你要建立 Kubernetes 集群,否则就不应该考虑 CRI-O。

*后一个是 containerd,它是云原生计算基金会即将推出的一个项目。它是一个守护进程,可作为各种容器运行时和操作系统的 API 接口。它后台依赖于 runc,它是 Docker 引擎的默认运行时。Google Kubernetes Engine(GKE)和 IBM Kubernetes Service(IKS)也在使用它。它是 Kubernetes容器运行时接口的一个实现(与 CRI-O 一样),因此是 Kubernetes 集群运行时的理想选择。

%title插图%num

镜像的检查与分发

 

*后一部分内容是镜像的检查与分发,主要是为了替代 docker inspect,并增加在远程仓库之间复制镜像的能力(可选)。

在这里,我要提到的唯一可以完成这些任务的工具是 Skopeo。它由红帽开发,是 Buildah、Podman 和 CRI-O 的附属工具。除了基本的 skopeo inspect(Docker 有相应的命令),Skopeo 还可以通过 skopeo copy 令来复制镜像,因此你可以直接在远程仓库之间复制镜像,无需将它们拉取到本地。如果你使用本地仓库,那么这个功能也可以作为拉取/推送。

另外,我还想提一下 Dive,这是一款检查、探索和分析镜像的工具。它更加人性化,提供了更加方便阅读的输出,而且还可以更深入地挖掘镜像,并分析和测量镜像的效率。此外,它也很适合在 CI 管道中使用,用于衡量你的镜像是否“足够高效”,或者换句话说,是否浪费了太多空间。

看穿容器的外表,Linux容器实现原理演示

容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”也就是独立的“运行环境”。下面我们使用 C 语言和 Namespace 技术来手动创建一个容器,演示 Linux 容器*基本的实现原理。

什么是容器?容器其实是一种特殊的进程而已,只是这个进程运行在自己的 “运行环境” 中,比如有自己的文件系统而不是使用主机的文件系统(文件系统这个对我来说印象是*深刻的,也是让人对容器很更好理解的一个切入点)。

有一个计算数值总和的小程序,这个程序的输入来自一个文件,计算完成后的结果则输出到另一个文件中。为了让这个程序可以正常运行,除了程序本身的二进制文件之外还需要数据,而这两个东西放在磁盘上,就是我们平常所说的一个“程序”,也就是代码的可执行镜像。

当“程序”被执行起来之后,它就从磁盘上的二进制文件变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合。像这样一个程序运行起来后的计算机执行环境的总和,就是进程,而计算机执行环境的总和就是它的动态表现。

而容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”也就是独立的“运行环境”。那么怎么去造成这个边界呢?

  • 对于 Docker 等大多数 Linux 容器来说,Cgroups 技术是用来制造约束的主要手段;
  • Namespace 技术则是用来修改进程视图的主要方法;

下面我们使用 C 语言和 Namespace 技术来手动创建一个容器,演示 Linux 容器*基本的实现原理。

%title插图%num

自己实现一个容器

Linux 中关于 Namespace 的系统调用主要有这么三个:

  • clone()—实现线程的系统调用,用来创建一个新的进程,同时可以设置 Namespace 的一些参数。
  • unshare()—使某个进程脱离某个 namespace。
  • setns()—把某进程加入到某个 namespace。

我们使用 clone 来创建一个子进程,通过创建出来的效果可以看到,子进程的 PID 是跟在父亲节点后面的,而不是 1。

  1. 1#define _GNU_SOURCE
  2. 2#include <sys/types.h>
  3. 3#include <sys/wait.h>
  4. 4#include <sys/mount.h>
  5. 5#include <stdio.h>
  6. 6#include <sched.h>
  7. 7#include <signal.h>
  8. 8#include <unistd.h>
  9. 9
  10. 10#define STACK_SIZE (1024 * 1024)
  11. 11static char container_stack[STACK_SIZE];
  12. 12
  13. 13char* const container_args[] = {
  14. 14    “/bin/bash”,
  15. 15    NULL
  16. 16};
  17. 17
  18. 18int container_main(void* arg) {
  19. 19    printf(“Container [%5d] – inside the container!\n”, getpid());
  20. 20    execv(container_args[0], container_args);
  21. 21    printf(“Something’s wrong!\n”);
  22. 22    return 1;
  23. 23}
  24. 24
  25. 25int main() {
  26. 26    printf(“Parent [%5d] – start a container!\n”, getpid());
  27. 27    int container_id = clone(container_main, container_stack + STACK_SIZE, SIGCHLD, NULL);
  28. 28    waitpid(container_id, NULL, 0);
  29. 29    printf(“Parent – container stopped!\n”);
  30. 30    return 0;
  31. 31}

%title插图%num

接下去这段代码我们给创建出来的进程设置 PID namespace 和 UTS namespace。从实际的效果我们可以看到子进程的 pid 为 1,而子进程中打开的 bash shell 显示的主机名为 container_dawn。是不是有点容器那味了?这里子进程在自己的 PID Namespace 中的 PID 为 1,因为 Namespace 的隔离机制,让这个子进程误以为自己是第 1 号进程,相当于整了个障眼法。但是,实际上这个进程在宿主机的进程空间中的编号不为 1,是一个真实的数值,比如 14624。

  1. 1int container_main(void* arg) {
  2. 2    printf(“Container [%5d] – inside the container!\n”, getpid());
  3. 3    sethostname(“container_dawn”15);
  4. 4    execv(container_args[0], container_args);
  5. 5    printf(“Something’s wrong!\n”);
  6. 6    return 1;
  7. 7}
  8. 8
  9. 9int main() {
  10. 10    printf(“Parent [%5d] – start a container!\n”, getpid());
  11. 11    int container_id = clone(container_main, container_stack + STACK_SIZE,
  12. 12                                CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
  13. 13    waitpid(container_id, NULL, 0);
  14. 14    printf(“Parent – container stopped!\n”);
  15. 15    return 0;
  16. 16}

%title插图%num

*后我们改变一下这个进程可以看到的文件系统,我们首先使用 docker export 将 busybox 镜像导出成一个 rootfs 目录,这个 rootfs 目录的情况如图所示,已经包含了 /proc、/sys 等特殊的目录。

%title插图%num

接下去,我们在代码中使用 chroot() 函数将创建出来的子进程的根目录改变成上述的 rootfs 目录。从实现的效果来看,创建出来的子进程的 PID 为 1,并且这个子进程将上述提到的 rootfs 目录当成了自己的根目录。

  1. 1char* const container_args[] = {
  2. 2    “/bin/sh”,
  3. 3    NULL
  4. 4};
  5. 5
  6. 6int container_main(void* arg) {
  7. 7    printf(“Container [%5d] – inside the container!\n”, getpid());
  8. 8
  9. 9    if (chdir(“./rootfs”) || chroot(“./”) != 0) {
  10. 10        perror(“chdir/chroot”);
  11. 11    }
  12. 12
  13. 13    execv(container_args[0], container_args);
  14. 14    printf(“Something’s wrong!\n”);
  15. 15    return 1;
  16. 16}
  17. 17
  18. 18int main() {
  19. 19    printf(“Parent [%5d] – start a container!\n”, getpid());
  20. 20    int container_id = clone(container_main, container_stack + STACK_SIZE,
  21. 21                                CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
  22. 22    waitpid(container_id, NULL, 0);
  23. 23    printf(“Parent – container stopped!\n”);
  24. 24    return 0;
  25. 25}

%title插图%num

需要注意的是所使用的 shell 需要改一下,因为 busybox 中没有 /bin/bash,假如还是 /bin/bash 的话是会报错的,因为 chroot 改变子进程的根目录视图之后,*终是从 rootfs/bin/  中找 bash 这个程序的。

上面其实已经基本实现了一个容器,接下去我们实现一下 Docker 卷的基本原理(假设你已经知道卷是什么了)。在代码中,我们将 /tmp/t1 这个目录挂载到 rootfs/mnt 这个目录中,并采用 MS_BIND 的方式,这种方式使得 rootfs/mnt (进入容器之后就是 mnt 目录)的视图其实就是 /tmp/t1 的视图,你对 rootfs/mnt 的修改其实就是对 /tmp/t1 修改,rootfs/mnt 相当于 /tmp/t1 的另一个入口而已。当然,在实验之前,你先确保 /tmp/t1 和 rootfs/mnt 这两个目录都已经被创建好了。实验效果见代码之后的那张图。

  1. 1char* const container_args[] = {
  2. 2    “/bin/sh”,
  3. 3    NULL
  4. 4};
  5. 5
  6. 6int container_main(void* arg) {
  7. 7    printf(“Container [%5d] – inside the container!\n”, getpid());
  8. 8
  9. 9    /*模仿 docker 中的 volume*/
  10. 10    if (mount(“/tmp/t1”“rootfs/mnt”“none”, MS_BIND, NULL)!=0) {
  11. 11        perror(“mnt”);
  12. 12    }
  13. 13
  14. 14    /* 隔离目录 */
  15. 15    if (chdir(“./rootfs”) || chroot(“./”) != 0) {
  16. 16        perror(“chdir/chroot”);
  17. 17    }
  18. 18
  19. 19    execv(container_args[0], container_args);
  20. 20    printf(“Something’s wrong!\n”);
  21. 21    return 1;
  22. 22}
  23. 23
  24. 24int main() {
  25. 25    printf(“Parent [%5d] – start a container!\n”, getpid());
  26. 26    int container_id = clone(container_main, container_stack + STACK_SIZE,
  27. 27                                CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
  28. 28    waitpid(container_id, NULL, 0);
  29. 29    printf(“Parent – container stopped!\n”);
  30. 30    return 0;
  31. 31}

%title插图%num

除了上述所使用的 PID、UTS、Mount namespace,Linux 操作系统还提供了 IPC、Network 和 User 这些 Namespace。

%title插图%num

总结

通过上面我们可以看到,容器的创建和普通进程创建没什么区别。都是父进程先创建一个子进程,只是对于容器来说,这个子进程接下去通过内核提供的隔离机制再给自己创建一个独立的资源环境。

同理,在使用 Docker 的时候,其实也并没有一个真正的 Docker 容器运行在宿主机里面。Docker 项目启动还是用户原来的应用进程,只是在创建进程的时候,Docker 为这个进程指定了它所需要启用的一组 Namespace 参数。这样,这个进程只能“看”到当前 Namespace 所限定的资源、文件、设备、状态或者配置。而对于宿主机以及其他不相关的程序,这个进程就完全看不到了。这时,进程就会以为自己是 PID Namespace 里面 1 号进程,只能看到各自 Mount Namespace 里面挂载的目录和文件,只能访问 Network Namespace 里的网络设备。这种就使得进程运行在一个独立的“运行环境”里,也就是容器里面。

因此,对接一开始所说的,还想再唠叨一句:容器其实就是一种特殊的进程而已。**只是这个进程和它运行所需的所有资源都打包在了一起,进程执行时所使用的资源也都是打包中的。相比虚拟机的方式,本质是进程的容器则仅仅是在操作系统上划分出了不同的“运行环境”,从而使得占用资源更少,部署速度更快。

Serverless 在大规模数据处理中的实践

前言

当您*次接触 Serverless 的时候,有一个不那么明显的新使用方式:与传统的基于服务器的方法相比,Serverless 服务平台可以使您的应用快速水平扩展,并行处理的工作更加有效。这主要是因为 Serverless 可以不必为闲置的资源付费,不用担心预留的资源不够。而在传统的使用范式中,用户必须预留成百上千的服务器来做一些高度并行化但执行时长较短的任务,而且必须为每一台服务器买单,即使有的服务器已经不再工作了。

以阿里云 Serverless 产品——函数计算为例,便可以完美解决您上述所有顾虑:

  • 如果您的任务本身计算量不是很大,但是有大量的并发任务请求需要并行处理, 比如多媒体文件处理、文档转换等;
  • 一个任务本身计算量很大,要求单个任务很快处理完,并且还能支持并行处理多个任务。

在这种场景下,用户唯一关注的就是:您的任务是可以分治拆解并且子任务是可以并行处理的,一个需要一个小时才能处理完的长任务,可以分解成 360 个独立的 10 秒长的子任务并行处理,这样,以前您要花一个小时才能处理完的任务,现在只需要 10 秒就可以搞定。由于采用的是按量计费的模型,完成的计算量和成本是大致相当的,而传统模型则因为预留资源肯定会存在浪费,浪费的费用也是需要您去承担的。

接下来,将详细阐述 Serverless 在大规模数据处理上的实践。

%title插图%num

*致弹性扩缩容应对计算波动

在介绍相关的大规模数据处理示例之前, 这里先简单介绍一下函数计算。

1. 函数计算简介

%title插图%num

  • 开发者使用编程语言编写应用和服务,函数计算支持的开发语言请参见开发语言列表;
  • 开发者上传应用到函数计算;
  • 触发函数执行:触发方式包括 OSS、API 网关、日志服务、表格存储以及函数计算 API、SDK 等;
  • 动态扩容以响应请求:函数计算可以根据用户请求量自动扩容,该过程对您和您的用户均透明无感知;
  • 根据函数的实际执行时间按量计费:函数执行结束后,可以通过账单来查看执行费用,收费粒度精确到 100 毫秒。

详情:函数计算官网

至此,您大约可以简单理解到函数计算是怎么运作的,接下来以大量视频并行转码的案例来阐述:假设一家在家教育或娱乐相关的企业,老师授课视频或者新的片源一般是集中式产生,而您希望这些视频被快速转码处理完以便能让客户快速看到视频回放。比如在当下疫情中,在线教育产生的课程激增,而出课高峰一般是 10 点、12 点、16 点、18 点等明显的峰值段,特定的时间内(比如半个小时)处理完所有新上传的视频是一个通用而且普遍的需求。

2. 弹性高可用的音视频处理系统

  • OSS 触发器

%title插图%num

如上图所示,用户上传一个视频到 OSS,OSS 触发器自动触发函数执行,函数计算自动扩容,执行环境内的函数逻辑调用 FFmpeg 进行视频转码,并且将转码后的视频保存回 OSS。

  • 消息触发器

%title插图%num

如上图所示,应用只需要发一个消息,自动触发函数执行音视频处理的任务即可,函数计算自动扩容,执行环境内的函数逻辑调用 FFmpeg 进行视频转码, 并且将转码后的视频保存回 OSS。

  • 直接手动调用 SDK 执行音视频处理任务

以 python 为例,大致如下:

  1. 1python
  2. 2    # -*- coding: utf-8 -*-
  3. 3    import fc2
  4. 4    import json
  5. 5
  6. 6    client = fc2.Client(endpoint=“http://123456.cn-hangzhou.fc.aliyuncs.com”,accessKeyID=“xxxxxxxx”,accessKeySecret=“yyyyyy”)
  7. 7
  8. 8    # 可选择同步/异步调用
  9. 9    resp = client.invoke_function(“FcOssFFmpeg”“transcode”, payload=json.dumps(
  10. 10    {
  11. 11        “bucket_name” : “test-bucket”,
  12. 12        “object_key” : “video/inputs/a.flv”,
  13. 13        “output_dir” : “video/output/a_out.mp4”
  14. 14    })).data
  15. 15
  16. 16    print(resp)

从上面我们也可以看出,触发函数执行的方式也很多,同时简单配置下 SLS 日志,就可以很快实现一个弹性高可用、按量付费的音视频处理系统,同时能提供免运维、具体业务数据可视化、强大自定义监控报警等超强功能的 dashboard。

%title插图%num

目前已经落地的音视频案例有 UC、语雀、躺平设计之家、虎扑以及几家在线教育的头部客户等,其中有些客户高峰期间,弹性使用到了万核以上 CPU 计算资源,并行处理的视频达到 1700+,同时提供了*高的性价比。

详情可以参考:

  • simple-video-processing
  • fc-oss-ffmpeg

%title插图%num

任务分治,并行加速

这种将任务分而治之的思想应用在函数计算上是一件有趣的事情,在这里举一个例子,比如您有一个超大的 20G 的 1080P 高清视频需要转码,即使您使用一台高配机器,需要的时间可能还是要按小时计,如果中途出问题中断转码,您只能重新开始再重复一遍转码的过程,如果您使用分治的思想+函数计算,转码的过程衍变为 分片-> 并行转码分片-> 合并分片,这样就可以解决您上述的两个痛点:

  • 分片和合成分片是内存级别的拷贝,需要的计算量*小,真正消耗计算量的转码,拆分成了很多子任务并行处理,在这个模型中,分片转码的*大时间基本等同于整个大视频的转码时间;
  • 即使中途某个分片转码出现异常,只需要重试这个分片的转码即可,不需要整个大任务推倒重来。

通过将大任务合理的分解,配合使用函数计算,编写一点 code,就可以快速完成一个弹性高可用、并行加速、按量付费的大型数据处理系统。

在介绍这个方案之前,我们先简单介绍一下 Serverless 工作流,Serverless 工作流可以很好地将函数和其他云服务和自建服务有组织地编排起来。

1. Serverless 工作流简介

Serverless 工作流(Serverless Workflow)是一个用来协调多个分布式任务执行的全托管云服务。在 Serverless 工作流中,您可以用顺序、分支、并行等方式来编排分布式任务,Serverless 工作流会按照设定好的步骤可靠地协调任务执行,跟踪每个任务的状态转换,并在必要时执行用户定义的重试逻辑,以确保工作流顺利完成。Serverless 工作流简化了开发和运行业务流程所需要的任务协调、状态管理以及错误处理等繁琐工作,让您聚焦业务逻辑开发。

详情:Serverless 工作流官网

接下来以一个大视频快速转码的案例来阐述 Serverless 工作编排函数,实现大计算任务的分解,并行处理子任务,*终达到快速完成单个大任务的目的。

2. 大视频的快速多目标格式转码

%title插图%num

如下图所示,假设用户上传一个 mov 格式的视频到 OSS,OSS 触发器自动触发函数执行,函数调用 FnF 执行,FnF 同时进行 1 种或者多种格式的转码(由 template.yml 中的 DST_FORMATS 参数控制),假设配置的是同时进行 mp4 和 flv 格式的转码。

  • 一个视频文件可以同时被转码成各种格式以及其他各种自定义处理,比如增加水印处理或者在 after-process 更新信息到数据库等;
  • 当有多个文件同时上传到 OSS,函数计算会自动伸缩,并行处理多个文件,同时每次文件转码成多种格式也是并行;
  • 结合 NAS + 视频切片,可以解决超大视频的转码,对于每一个视频,先进行切片处理,然后并行转码切片,*后合成,通过设置合理的切片时间,可以大大加快较大视频的转码速度;
  • fnf 可以跟踪每一步执行情况,并且可以自定义每一个步骤的重试,提高任务系统的鲁棒性,如:retry-example

详情可以参考:fc-fnf-video-processing

在任务分治,并行加速具体的案例中,上面分享的是 CPU 密集型任务分解,但也可以进行 IO 密集型任务分解,比如这个需求:上海的 region 的 OSS bucket 中的一个 20G 大文件,秒级转存回杭州的 OSS Bucket 中。这里也可以采用分治的思路,Master 函数在接到转存任务之后,将超大文件进行分片的 range 分配给每个 Worker 子函数,Worker 子函数并行转存属于自己那部分的分片,Master 函数待所有子 Worker 运行完毕之后,提交合并分片请求,完成整个转存任务。

%title插图%num

详情可以参考:利用函数计算多实例并发实现秒级转存超大文件

%title插图%num

总结

本文探讨了 Serverless 服务平台可以使您的应用快速水平扩展,并行处理的工作更加有效,并给出了具体的实践案例,无论在 CPU 密集型还是 IO 密集型场景,函数计算 + Serverless 都能完美解决您以下顾虑:

  • 不必为闲置的资源付费
  • 不用担心计算资源预留不够
  • 大计算量的任务需要快速处理完毕
  • 更好的任务流程跟踪
  • 完善的监控报警、免运维、业务数据可视化等

分布式架构的王者?Kubernetes凭什么

说起kubernetes,主要是通过动态伸缩容来解决容器扩容的问题。随着技术的不断发展,微服务对互联网架构的不断影响,微服务对运维行业的压力也造成了非常大的影响,kubernetes已经在devops领域,占据了非常重要的地位,或者毫不夸张的说,已经可以成为一个行业的标准了。

kubernetes是一个分布式架构的王者,解决了微服务的很多技术难题和运维烦恼,但是它并不是一下就进化而来,ta到底是要解决什么样的问题,适合哪些的应用场景呢?现在的kubernetes有哪些缺陷,未来的技术发展方向是哪里呢?我们更加应该关注这些,才能让我们更好的把握好趋势,体会到技术的魅力。

%title插图%num

虚拟化

在熟悉kubernets之前,我们应该先了解一下虚拟化,kubernetes本身就是一个虚拟化的产品。理解虚拟化,可以帮助我们更好的理解kubernetes是如何帮助我们解决行业痛点,为何如此的被大众如此热衷的。

1. 什么是虚拟化

在计算机中,虚拟化(英语:Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如服务器、⽹络、内存及存储等,予以抽象、转换后呈现出来,打破实体结构间的不可切割的障碍,使⽤户可以⽐原本的组态更好的⽅式来应⽤这些资源。这些资源的新虚拟部分是不受现有资源的架设⽅式,地域或物理组态所限制。⼀般所指的虚拟化资源包括计算能⼒和资料存储。

2. 虚拟化的分类

根据在整个系统中的位置不同,虚拟化架构分为以下几种:

  • 寄居虚拟化架构
  • 裸金属虚拟化架构
  • 操作系统虚拟化架构
  • 混合虚拟化架构

1)寄居虚拟化架构

寄居虚拟化架构指在宿主操作系统之上安装和运行虚拟化程序,依赖于宿主操作系统对设备的支持和物理资源的管理。(类似 Vmware Workstation 的程序)

%title插图%num

2)裸金属虚拟化架构

裸金属虚拟化架构指直接在硬件上面安装虚拟化软件,再在其上安装操作系统和应用,依赖虚拟层内核和服务器控制台进行管理。

%title插图%num

3)操作系统虚拟化架构

操作系统虚拟化架构在操作系统层面增加虚拟服务器功能。操作系统虚拟化架构把单个的操作系统划分为多个容器,使用容器管理器来进行管理。

宿主操作系统负责在多个虚拟服务器(即容器)之间分配硬件资源,并且让这些服务器彼此独立。

%title插图%num 4)混合虚拟化架构

混合虚拟化架构将一个内核级驱动器插入到宿主操作系统内核。这个驱动器作为虚拟硬件管理器来协调虚拟机和宿主操作系统之间的硬件访问。

%title插图%num

3. 虚拟化的特点

虚拟化具有以下特点:

  • 分区:对物理机分区,可实现在单一物理机上同时运行多个虚拟机。
  • 隔离:同一物理机上多个虚拟机相互隔离。
  • 封装:整个虚拟机执行环境封装在独立文件中。
  • 独立:虚拟机无须修改,可运行在任何物理机上。

%title插图%num

常用的虚拟化技术

1. openstack

OpenStack:开源管理项⽬ OpenStack是⼀个旨在为公共及私有云的建设与管理提供软件的开源项⽬。它不是⼀个软件,⽽是由⼏个主要的组件组合起来完成⼀些具体的⼯作。OpenStack由以下五个相对独⽴的组件构成:

  • OpenStack Compute(Nova)是⼀套控制器,⽤于虚拟机计算或使⽤群组启动虚拟机实例;
  • OpenStack镜像服务(Glance)是⼀套虚拟机镜像查找及检索系统,实现虚拟机镜像管理;
  • OpenStack对象存储(Swift)是⼀套⽤于在⼤规模可扩展系统中通过内置冗余及容错机制,以对象为单位的存储系统,类似于Amazon S3;
  • OpenStack Keystone,⽤于⽤户身份服务与资源管理以及
  • OpenStack Horizon,基于Django的仪表板接⼝,是个图形化管理前端。这个起初由美国国家航空航天局和Rackspace在2010年末合作研发的开源项⽬,在打造易于部署、功能丰富且易于扩展的云计算平台。OpenStack项⽬的⾸要任务是简化云的部署过程并为其带来良好的可扩展性,企图成为数据中⼼的操作系统,即云操作系统。

2. KVM

KVM(Kernel-based Virtual Machine)基于内核的虚拟机 KVM是集成到Linux内核的Hypervisor,是X86架构且硬件⽀持虚拟化技术(Intel VT或AMD-V)的Linux的全虚拟化解决⽅案。它是Linux的⼀个很⼩的模块,利⽤Linux做⼤量的事,如任务调度、内存管理与硬件设备交互等。KVM*⼤的好处就在于它是与Linux内核集成的,所以速度很快

3. VMWare

VMWare (Virtual Machine ware)是⼀个“虚拟PC”虚拟机管理管理软件。它的产品可以使你在⼀台机器上同时运⾏两个或更多Windows、 DOS、LINUX系统。与“多启动”系统相⽐,VMWare采⽤了完全不同的概念。多启动系统在⼀个时刻只能运⾏⼀个系统,在系统切换时需要重新启动机器。VMWare是真正“同时”运⾏,多个操作系统在主系统的平台上,就象标准Windows应⽤程序那样切换。⽽且每个操作系统你都可以进⾏虚拟的分区、配置⽽不影响真实硬盘的数据,你甚⾄可以通过⽹卡将⼏台虚拟机⽤⽹卡连接为⼀个局域⽹,*其⽅便。安装在VMware操作系统性能上⽐直接安装在硬盘上的系统低不少,因此,⽐较适合学习和测试。

%title插图%num

容器和云计算

1. 容器发展史

1)Chroot

容器技术的概念可以追溯到1979年的UNIX Chroot。这项功能将Root⽬录及其它⼦⽬录变更⾄⽂件系统内的新位置,且只接受特定进程的访问,其设计⽬的在于为每个进程提供⼀套隔离化磁盘空间。1982年其被添加⾄BSD。

chroot只是提供了对进程⽂件⽬录虚拟化的功能,不能够防⽌进程恶意访问系统。这个问题在FreeBSDGails容器技术中得以解决

2)FreeBSD Jails

FreeBSD Jails与Chroot的定位类似,不过其中包含有进程沙箱机制以对⽂件系统、⽤户及⽹络等资源进⾏隔离。通过这种⽅式,它能够为每个Jail、定制化软件安装包乃⾄配置⽅案等提供⼀个对应的IP地址。Jails技术为FreeBSD系统提供了⼀种简单的安全隔离机制。它的不⾜在于这种简单性的隔离也同时会影响Jails中应⽤访问系统资源的灵活性。

3)Solaris Zones

Solaris Zone技术为应⽤程序创建了虚拟的⼀层,让应⽤在隔离的Zone中运⾏,并实现有效的资源管理。每⼀个Zone 拥有⾃⼰的⽂件系统,进程空间,防⽕墙,⽹络配置等等。

Solaris Zone技术真正的引⼊了容器资源管理的概念。在应⽤部署的时候为Zone配置⼀定的资源,在运⾏中可以根据Zone的负载动态修改这个资源限制并且是时⽣效的,在其他Zone不需要资源的时候,资源会⾃动切换给需要的资源的Zone,这种切换是即时的不需要⼈⼯⼲预的,*⼤化资源的利⽤率,在 必要的情况下,也可以为单个Zone隔离⼀定的资源。

4)LXC

LXC指代的是Linux Containers,其功能通过Cgroups以及Linux Namespaces实现。也是第⼀套完整的Linux容器管理实现⽅案。在LXC出现之前,Linux上已经有了类似Linux-Vserver、OpenVZ和FreeVPS。虽然这些技术都已经成熟,但是这些解决⽅案还没有将它们的容器⽀持集成到主流 Linux 内核。相较于其它容器技术, LXC能够在⽆需任何额外补丁的前提下运⾏在原版Linux内核之上。⽬前LXC项⽬由Canonical有限公司负责赞助及托管

5)Docker

Docker项⽬*初是由⼀家名为DotCloud的平台即服务⼚商所打造,其后该公司更名为Docker。Docker在起步阶段使⽤LXC,⽽后利⽤⾃⼰的Libcontainer库将其替换下来。与其它容器平台不同,Docker引⼊了⼀整套与容器管理相关的⽣态系统。其中包括套⾼效的分层式容器镜像模型、⼀套全局及本地容器注册表、⼀个精简化REST API以及⼀套命令⾏界⾯等等。

与Docker具有同样⽬标功能的另外⼀种容器技术就CoreOS公司开发的Rocket. Rocket基于AppContainer规范并使其成为⼀项更为开放的标准

2. Docker容器

Docker简单高效的思想, 快速的被广大的用户所接受。

1)Docker历史

2010年,⼏个搞IT的年轻⼈在美国旧⾦⼭成⽴了⼀家名叫“dotCloud”的公司。这家公司主要提供基于PaaS的云计算技术服务。具体来说,是和LXC有关的容器技术。LXC,就是Linux容器虚拟技术(Linux container)、后来,dotCloud公司将⾃⼰的容器技术进⾏了简化和标准化,并命名为——Docker。

Docker技术诞⽣之后,并没有引起⾏业的关注。⽽dotCloud公司,作为⼀家⼩型创业企业,在激烈的竞争之下,也步履维艰。正当他们快要坚持不下去的时候,脑⼦⾥蹦出了“开源”的想法。

什么是“开源”?开源,就是开放源代码。也就是将原来内部保密的程序源代码开放给所有⼈,然后让⼤家⼀起参与进来,贡献代码和意⻅。

Open Source,开源有的软件是⼀开始就开源的。也有的软件,是混不下去,创造者⼜不想放弃,所以选择开源。⾃⼰养不活,就吃“百家饭”嘛。

2013年3⽉,dotCloud公司的创始⼈之⼀,Docker之⽗,28岁的Solomon Hykes正式决定,将Docker项⽬开源。

不“开”则已,⼀“开”惊⼈。

越来越多的IT⼯程师发现了Docker的优点,然后蜂拥⽽⾄,加⼊Docker开源社区。Docker的⼈⽓迅速攀升,速度之快,令⼈瞠⽬结⾆。开源当⽉,Docker 0.1版本发布。此后的每⼀个⽉,Docker都会发布⼀个版本。到2014年6⽉ 9⽇,Docker 1.0版本正式发布。

此时的Docker,已经成为⾏业⾥⼈⽓*⽕爆的开源技术,没有之⼀。甚⾄像Google、微软、Amazon、 VMware这样的巨头,都对它⻘睐有加,表示将全力⽀持。

Docker⽕了之后,dotCloud公司⼲脆把公司名字也改成了Docker Inc.。Docker和容器技术为什么会这么⽕爆?说⽩了,就是因为它“轻”, 简单、快速。

2)Docker的原理

容器是⼀种轻量级的虚拟化技术,因为它跟虚拟机⽐起来,它少了⼀层 hypervisor 层。

hypervisor是⼀种运⾏在物理服务器和操作系统之间的中间层软件,可以允许多个操作系统和应⽤共享⼀套基础物理硬件。可以将hypervisor看做是虚拟环境中的“元”操作系统,可以协调访问服务器上的所有物理设备和虚拟机,所以⼜称为虚拟机监视器(virtual machine monitor)。hypervisor是所有虚拟化技术的核⼼,⾮中断的支持多⼯作负载迁移是hypervisor的基本功能。当服务器启动并执⾏hypervisor时,会给每⼀台虚拟机分配适量的内存、CPU、网络和磁盘资源,并且加载所有虚拟机的客户操作系统。

对于容器来说,*重要的是怎么保证这个进程所⽤到的资源是被隔离和被限制住的,在 Linux 内核上⾯是由 cgroup 和 namespace 这两个技术来保证的。

3)NameSpace

namespace 是⽤来做资源隔离的,在Linux 内核上有七种 namespace,docker 中⽤到了前六种。第七种cgroup namespace 在 docker 本身并没有⽤到,但是在 runC 实现中实现了 cgroup namespace。


%title插图%num

容器编排技术

随着微服务技术的发展,容器也给我们带来了一些新的挑战:

  • 怎么去管理那么多的容器
  • 怎么横向扩展
  • 容器down了,怎么恢复
  • 更新容器后如何灰度发布
  • 如何监控容器
  • 新创建的容器如何加入服务
  • 数据安全问题

1. 常用的docker编排工具

1)docker-compose

Docker-Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。

2)swarm

⽬前三⼤主流的容器平台Swarm,Mesos和Kubernetes具有不同的容器调度系统;Swarm的特点是直接调度Docker容器,并且提供和标准Docker API⼀致的API。

每台服务器上都装有Docker并且开启了基于HTTP的DockerAPI。这个集群中有⼀个SwarmManager的管理者,⽤来管理集群中的容器资源。管理者的管理对象不是服务器层⾯⽽是集群层⾯的,也就是说通过Manager,我们只能笼统地向集群发出指令⽽不能具体到某台具体的服务器上要⼲什么(这也是Swarm的根本所在)。⾄于具体的管理实现⽅式,Manager向外暴露了⼀个HTTP接⼝,外部⽤户通过这个HTTP接⼝来实现对集群的管理

3)Mesos

Mesos针对不同的运⾏框架采⽤相对独⽴的调度系统,其框架提供了Docker容器的原⽣⽀持。Mesos并不负责调度⽽是负责委派授权,毕竟很多框架都已经实现了复杂的调度

4)Kubernetes

Kubernetes则采⽤了Pod和Label这样的概念把容器组合成⼀个个的互相存在依赖关系的逻辑单元。Pod中可以部署容器, 目前集成的是docker容器, 如果docker哪一天不流行了,pod的存在可以轻松切换。

相关容器被组合成Pod后被共同部署和调度,形成服务(Service)。这个是Kubernetes和Swarm,Mesos的主要区别。

Kubernetes(k8s)是⾃动化容器操作的开源平台,这些操作包括部署,调度和节点集群间扩展。如果你曾经⽤过Docker容器技术部署容器,那么可以将Docker看成Kubernetes内部使⽤的低级别组件。

Kubernetes不仅仅⽀持Docker,还⽀持Rocket,这是另⼀种容器技术。

使⽤Kubernetes可以:

  • ⾃动化容器的部署和复制
  • 随时扩展或收缩容器规模
  • 将容器组织成组,并且提供容器间的负载均衡
  • 很容易地升级应⽤程序容器的新版本
  • 提供容器弹性,如果容器失效就替换它