Kubernetes-高级平台开发-二-

120 阅读1小时+

Kubernetes 高级平台开发(二)

原文:Advanced Platform Development with Kubernetes

协议:CC BY-NC-SA 4.0

四、平台内 CI/CD

CI/CD 代表持续集成和交付,以及持续集成和部署。连续交付包括将代码编译或准备成合适版本的过程,而连续部署通过自动化过程安装或更新现有应用(通常在服务器上)。CI/CD 继续成熟并扩大其范围,从低级开发关注到中央平台操作。它已经超越了为独立软件包合并和编译应用的局限。CI/CD 正在取得新的进展,部署机器学习模型、无服务器功能,甚至基础设施供应。本章利用 CI/CD 来开发和交付用于在应用平台范围内实现数据科学功能的容器。本章使用 GitLab 为平台开发和生产运营提供 CI/CD 功能。

开发和运营

CI/CD 原则已经从驱动传统软件应用开发的孤立的构建和交付系统扩展开来。CI/CD 概念正在扩展到企业平台的操作组件中。从学术实验室到基于云的生产部署,数据科学领域,特别是机器学习领域有着广泛的实现和专门的流程。机器学习自动化已经接受了容器化,以及自动构建容器来包装复杂的逻辑和依赖关系。CI/CD 风格的流水线非常适合于容器化人工智能的实验、测试和生产部署,降低了部署和回滚功能的成本和复杂性。

平台集成

本章集成了 GitLab 和 Kubernetes,以形成平台开发和运营之间更紧密的关系。从 Kubernetes 集群内部的版本化源代码构建容器为在集成、交付和部署的所有阶段访问平台功能提供了新的机会。

在第二章中,图 2-3 说明了在单节点 Kubernetes (k3s)集群上运行的 GitLab 实例与通过gitkubectl命令从外部控制的远程开发集群之间的关系。从平台内部集成gitkubectl的能力提供了将开发能力引入平台的机会。Kubeflow 是一个流行的机器学习自动化平台,它通过包含kubectl的自定义 JupyterLab 1 映像来拥抱这个概念。JupyterLab 映像由 JupyterHub 2 提供,并使用 RBAC 定义的权限挂载一个 Kubernetes 服务帐户。以下练习借用了其中一些概念来演示 CI/CD 与数据科学功能的集成,作为更深入定制的起点(参见图 4-1 )。

img/483120_1_En_4_Fig1_HTML.jpg

图 4-1

与 GitLab 和 JupyterLab 的 CI/CD 集成

又一个发展集群

本章使用 Scaleway 3 (一家欧洲折扣云提供商)的四节点定制 Kubernetes 集群设置。尽管本书中的许多例子使用了不同的提供者,但是除了展示与供应商无关的方法以及概念和实现的可移植性之外,没有必要将服务分散到不同的提供者中。Scaleway 是一个经济高效的选项,可用于测试各种集群配置。以下集群使用一个 DEV1-M (3 个 CPU/4G RAM/40G SSD)作为 Kubernetes 主节点,三个 DEV1-L (4 个 CPU/8G RAM/80GB)作为工作节点。在撰写本文时,这个开发集群的总成本不到每小时 0.20 美元。Scaleway 提供了与 Vultr 和 Digital Ocean 类似的用户界面和选项。“开发环境”一章中的说明可能适用于 Scaleway 和许多其他供应商。

本章的剩余部分使用 GitLab 托管一个包含Dockerfile的存储库,使用新的数据科学和机器学习功能以及 kubectl 命令定制基本 JupyterLab 映像。这个映像是用 GitLab 的 CI 特性构建的,托管在 GitLab 映像注册表中。第二章讲述在单节点 Kubernetes 集群上建立 GitLab 然而,任何 GitLab 实例都是合适的。

角色访问控制

GitLab 使用具有cluster-admin权限的服务帐户令牌。请记住 GitLab integration“安全性是基于一种模型,在这种模型中,开发人员是可信的,因此应该只允许可信的用户控制您的集群。”4

为了与第 2 和第三章的清单组织保持一致,在 k8s git 项目中创建目录cluster-apk8s-dev2/000-cluster/40-gitlab-integration

在清单 4-1 中名为05-rbac.yml的文件中创建 ServiceAccount 和 ClusterRoleBinding。

     apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: data-lab
  namespace: data-lab
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: data-lab
subjects:
  - kind: ServiceAccount
    name: data-lab
    namespace: data-lab
---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: hub
  namespace: data-lab
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: hub
subjects:
  - kind: ServiceAccount
    name: hub
    namespace: data

Listing 4-1ServiceAccount and ClusterRoleBinding for GitLab

应用 GitLab RBAC 配置:

$ kubectl apply -f 05-rbac.yml

GitLab 集团库柏访问

虽然 GitLab 的 Kubernetes 集成是建立在信任开发者的基础上的,但并不是所有的项目/资源库,或者开发者,都需要访问 Kubernetes。GitLab 允许单个项目或小组分别集成 Kubernetes 集群。本章创建了一个名为 Data Science (ds)的新组,并将其与 Scaleway 上设置的新 dev2 集群集成。图 4-2 展示了如何建立一个新的 GitLab 组。

img/483120_1_En_4_Fig2_HTML.jpg

图 4-2

创建 GitLab 组

配置 Kubernetes 集群集成

配置新的数据科学 GitLab 组以控制 Kubernetes 集群(参见图 4-3 ):

img/483120_1_En_4_Fig3_HTML.jpg

图 4-3

gitlab group kublers configuration menu-gitlab 群组库设定功能表

  1. 在组的左侧菜单中选择 Kubernetes

  2. 选择选项卡添加现有集群

  3. 为集群提供一个名称,在本例中为 dev2

  4. 提供主节点上公开的 Kubernetes API 的全限定 URL(例如, https://n1.dev2.apk8s.dev:6443 )。

  5. 提供群集 CA 证书。该证书很容易在默认名称空间的default-token中找到。要检索所需 PEM 格式的证书,首先在默认名称空间中列出秘密:kubectl get secrets。如果这是一个新的星团,那么default-token将可能是唯一的秘密。使用以下命令,用default-token替换<secret name>:

    kubectl get secret \
      $(kubectl get secret | grep default-token | awk '{print $1}') -o jsonpath="{['data']['ca\.crt']}" \
    | base64 --decode
    
    
  6. 从上一节中设置的gitlab-admin服务帐户中提供服务令牌。使用以下命令:

    kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')
    
    
  7. 确保 RBAC 已启用。

启用依赖关系

本章使用安装在 dev2 集群上的 GitLab Runner 5 来构建带有 GitLab CI 的定制容器。 6 完成数据科学组的 Kubernetes 整合(见图 4-4 ):

  1. 提供一个基本域。虽然在本章中没有使用,GitLab 可以使用这个基域进行自动开发 7 和 Knative 8 集成。 dev2 的设置遵循第三章中的 DNS 指令,为*.dev2分配多个A记录,并分配每个工作节点的公共 IP 地址。dev2.apk8s.dev的任何子域都将解析为集群上的一个工作节点。

  2. 安装 Helm 柄。 9 GitLab 将 Helm 安装到集群上一个名为gitlab-managed-apps的新命名空间中。GitLab 使用 Helm 图在幕后管理其依赖的应用。Helm Tiller 可能已经安装在集群中,并且正在另一个命名空间中运行;但是,GitLab 需要自己的 Helm 柄。安装可能需要几分钟。新发布的头盔 3 不需要头盔 Helm 杆,请查看 GitLab 文档了解已安装的版本。

  3. 最后,安装 GitLab Runner。安装可能需要几分钟。

img/483120_1_En_4_Fig4_HTML.jpg

图 4-4

在 Kubernetes 集群上安装应用

自定义 JupyterLab 映像

Jupyter 笔记本 10 (图 4-8 )正在成为基于 Python 的数据科学的主要工具。Jupyter 笔记本结合了实时可运行代码和基于 markdown 的文本片段,非常适合描述和演示计算过程及其相应的结果。JupyterLab 是下一代 Jupyter 笔记本电脑,结合了更新的用户界面和集成的文件浏览器,以及用于运行多个笔记本和终端的选项卡。JupyterLab(见图 4-6 )提供了一个健壮的集成开发环境,能够在一个容器中运行。

JupyterHub 是一个应用,设计用于在单个或多用户环境中提供 Jupyter 笔记本。“数据科学”一章探讨了如何使用 JupyterHub 来提供定制的 JupyterLab 映像。在平台内运行 JupyterHub 使软件开发人员、数据科学家和统计人员能够直接访问平台服务,包括文件系统、数据库、事件队列和对 Kubernetes API 的许可访问,如图 4-5 所示。

img/483120_1_En_4_Fig5_HTML.jpg

图 4-5

Kubernetes 星团中的 JupyterLab

本章演示了如何使用 GitLab CI 来自动化构建自定义 JupyterLab 容器映像并将其推入 GitLab 的集成容器注册表的过程。

img/483120_1_En_4_Fig6_HTML.jpg

图 4-6

使用 Python 和 Octave 内核的自定义 JupyterLab,以及kubectl

存储库和容器源

在 GitLab 组数据科学(ds)中创建一个名为notebook-apk8s的新项目。该项目从一个 Dockerfile 文件开始。

用清单 4-2 的内容创建一个名为Dockerfile的文件。

FROM jupyter/minimal-notebook:7a3e968dd212

USER root

ENV DEBIAN_FRONTEND noninteractive
RUN apt update \
    && apt install -y apt-transport-https curl iputils-ping gnupg
RUN curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg \
    | sudo apt-key add -
RUN echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" \
    | sudo tee -a /etc/apt/sources.list.d/kubernetes.list

RUN apt update \
    && apt install -y kubectl git gcc mono-mcs musl-dev octave \
    && rm -rf /var/lib/apt/lists/*

# kubefwd for local development and testing
RUN apt-get clean && apt-get autoremove --purge
RUN wget https://github.com/txn2/kubefwd/releases/download/v1.8.4/kubefwd_amd64.deb \
    && dpkg -i kubefwd_amd64.deb \
    && rm kubefwd_amd64.deb

USER $NB_UID

# Installs data science and machine learning Python packages
RUN pip install --no-cache \
    rubix \
    python-gitlab \
    scipy \
    numpy \
    pandas \
    scikit-learn \
    matplotlib \
    tensorflow \
    torch \
    torchvision \
    fastai \
    octave_kernel \
    jupyterlab-git

# JupyterLab and server extensions
RUN jupyter labextension install @jupyterlab/git
RUN jupyter labextension install @jupyterlab/plotly-extension
RUN jupyter serverextension enable --py jupyterlab_git

Listing 4-2Custom JupyterLab

新的Dockerfile扩展了官方的最小 Jupyter 笔记本容器, 11 增加了 Kubernetes 配置实用程序kubectl以及几个流行的基于 Python 的机器学习库。Jupyter 笔记本支持除 Python 之外的大量语言,包括 Octave、12MathWorks 对 MATLAB 13 的开源替代。

通过构建本地 Docker 映像来测试新的Dockerfile。从包含Dockerfile,运行的目录中:

docker build -t jupyterlab-apk8s

构建自定义 Jupyter 笔记本可能需要几分钟时间。基本的jupyter/minimal-notebook大约有 3GB,在添加了十几个机器学习和数据科学包及其依赖项之后,新的jupyterlab-apk8s映像将近 7GB。

本地测试

在用于构建新容器的同一本地工作站上,用 Docker 运行并测试新容器。尽管新的jupyterlab-apk8s映像旨在从集群内部运行,但是安装了一个名为kubefwd的实用程序,以适应从远程 Kubernetes 集群到本地服务名称的端口转发。使用以下命令启动jupyterlab-apk8s:

docker run --rm --name jl -p 8888:8888 \
  -v "$(pwd)":"/home/jovyan/work" \
  -v "$HOME/.kube/apk8s-dev2":"/home/jovyan/.kube/config" \
  --user root \
  -e GRANT_SUDO=yes \
  -e JUPYTER_ENABLE_LAB=yes -e RESTARTABLE=yes \
  jupyterlab-apk8s:latest

docker run命令将当前工作目录挂载到/home/jovyan/work,容器以用户jovyan,的身份运行,容器内的初始工作目录是/home/jovyan/。下一个卷挂载(-v)公开了新 dev2 集群的配置apk8s-dev2。参数--user root以 root 用户身份启动容器,并且是 kubefwd 实用程序所需的sudo访问所必需的,并通过-e GRANT_SUDO=yes启用。关于 Jupyter 笔记本底座容器暴露的特性列表,请参见官方文档 14

Note

用户jovyan是 Jupyter 社区的惯例,用于所有官方的 Jupyter 笔记本图片。名词 Jovian 是虚构的木星居民。

在启动新的 Jupyter Notebook 容器时,查看包含令牌的连接指令的初始日志输出。从容器输出中检索令牌后,访问 URL: http://localhost:8888?token=<token>

图 4-6 表示新jupyterlab-apk8s容器的运行实例。

端口转发

新的jupyterlab-apk8s容器旨在运行在由 JupyterHub 提供的 dev2 集群中。然而,在本地工作站上的测试可以在kubefwd 15 (developed by the author)的帮助下模拟集群内的环境,该软件先前安装在jupyterlab-apk8s中用于本地测试支持。

在 Docker 容器内(在localhost:8888),在运行 Jupyter 笔记本内标题为其他的部分下,选择终端。一旦终端启动,提供以下命令来端口转发在 dev2 集群上的monitoring名称空间中运行的所有服务(参见图 4-7 ):

img/483120_1_En_4_Fig7_HTML.jpg

图 4-7

端口转发远程 Kubernetes 集群

sudo kubefwd svc -n monitoring

实用程序kubefwd将远程 Kubernetes 集群上的 Pods backing 服务连接到本地工作站(在本例中为 Jupyter 笔记本)上的一组匹配的 DNS 名称和端口,并进行端口转发。一旦kubefwd开始运行,就可以连接到 http://prometheus-k8s.monitoring:9200 等服务,就像它们来自远程集群一样。

测试笔记本

创建一个新的笔记本来测试自定义 Jupyter 容器jupyterlab-apk8s。在运行笔记本的文件菜单下,选择新建笔记本,在下拉列表中选择 Python 3 内核。将清单 4-3 中的 Python 代码输入到第一个输入单元格中,点击标签下菜单栏中的 play 按钮,或者使用键盘快捷键 Shift-Enter (见图 4-8 )。

import requests

response = requests.get('http://prometheus-k8s:9090/api/v1/query',
    params={'query': "node_load1"})

if response.status_code == 200:
    print("Got 200 response from Prometheus")

for node_data in response.json()['data']['result']:
    value = node_data['value'][1]
    name = node_data['metric']['instance']
    print(f'Node {name} has a 1m load average of {value}')

Listing 4-3Python communicating with Prometheus

新创建的 Jupyter Notebook,Untitled.ipynb执行单个单元,该单元向 Prometheus 返回连接状态以及 dev2 集群中每个 Kubernetes 节点的当前一分钟平均负载,如图 4-8 所示。

img/483120_1_En_4_Fig8_HTML.jpg

图 4-8

与 Prometheus 交流

额外学习

如果你是数据科学或机器学习的新手,这个定制的笔记本包含几个流行的库和框架供你入门。人工智能和机器学习的热门在线课程经常使用 Jupyter 笔记本或 MATLAB/Octave。Coursera 使用 MATLAB/Octave 提供了最受欢迎的机器学习课程之一 16 ,由斯坦福大学教授、谷歌大脑联合创始人吴恩达教授。Udacity 提供了一门介绍性的纳米学位课程,名为用 Python 进行 API 编程、 17 、大量使用 Jupyter 笔记本。杰瑞米·霍华德的 18 fast.ai 在程序员机器学习入门 19 课程中提供了一种独特的自上而下的方法,fast.ai Python 库包含在这个自定义映像中。

Jupyter 笔记本环境并不局限于预装的 Python 库;开发人员和数据科学家可以使用pip install来扩展 Python 环境,以满足他们的需求。命令jupyter labextension 20jupyter serverextension也可用于扩展和定制 Jupyter 生态系统。 21

自动化

新的 Jupyter Notebook jupyterlab-apk8s 容器映像(在本章前面创建并在上一节测试)无需进一步修改即可使用。因为这个容器不包含专有代码或业务逻辑,所以它可能被上传到任何公共容器注册中心,包括 Docker Hub。以下命令使用 apk8s Docker Hub 帐户标记新映像,并将其推送到注册表,使其可供公众使用:

docker tag jupyterlab-apk8s:latest \
apk8s/jupyterlab-apk8s:latest

docker push apk8s/jupyterlab-apk8s:latest

不是所有的容器都应该是可公开访问的,因为它们可能包含专有的业务逻辑或数据。除了私有容器注册的安全方面之外,保持对这些资产的控制,同时避免供应商锁定,也是一个很好的实践。幸运的是,开源 GitLab(安装在第二章)自带内置容器注册表。

构建jupyterlab-apk8s容器是一个简单的手动过程。新构建的jupyterlab-apk8s容器的版本与创建它的代码没有耦合。一个开发人员可能很容易做出改变,却忘记为其他开发人员推出新版本。自动化不仅解决了在代码版本和映像版本之间实施耦合的问题,而且还开放了添加测试套件和安全检查的能力。

本章的剩余部分使用 GitLab CI 来自动构建和存储前面定义的jupyterlab-apk8s容器。早些时候在 GitLab 中建立的新数据科学组已经被配置为通过 GitLab Runners 在新的 dev2 集群上执行 GitLab CI 操作。下一节将介绍如何为自动化容器构建和版本控制编写 GitLab CI 配置。

GitLab 我们

如今有大量功能强大的 CI/CD 工具可用。Jenkins、Buildbot、Drone、Concourse 和许多其他设计良好的工具暴露了对复杂的软件集成、交付和部署过程自动化的复杂而稳定的方法的高度需求。

GitLab CI 与 GitLab 本身有深度融合,需要它运行;然而,这并不意味着一个项目只能由 GitLab 管理。Git VCS 允许多个远程,允许一个项目存在于多个托管的存储库中,如 GitHub 或 Bitbucket。GitLab 还支持基于的项目与存储库镜像的同步。 22 本书没有管理另一个应用,而是专门使用 GitLab CI 来集成 GitLab 和 Kubernetes。

GitLab CI 需要一个 GitLab 项目和一个 GitLab Runner。在本章的前面,一个名为 Data Science ( gitlab.apk8s.dev/ds)的 GitLab 组被配置,在为该组配置 Kubernetes 访问之后,安装了一个 GitLab Runner。在这个新的数据科学小组中,项目jupyterlab-apk8s ( gitlab.apk8s.dev/ds/jupyterlab-apk8s)得到了开发,并包括在前面部分构建和测试的单个Dockerfile。调用 GitLab CI 需要一个文件.gitlab-ci.yml,这将在下一节中介绍。

。吉塔实验室

当 GitLab 项目中的任何源存储库包含一个名为.gitlab-ci.yml的文件时,该文件的存在会调用 GitLab CI,它会创建一个新的流水线并开始运行所有定义的作业。在新的jupyterlab-apk8s项目中创建一个名为的文件,并添加清单 4-4 的内容。

stages:
  - build
  - test
  - deploy

tag_build:
  stage: build
  only:
    - tags@ds/jupyterlab-apk8s
  image:
    # debug version is required for shell
    name: gcr.io/kaniko-project/executor:debug-v0.10.0
    entrypoint: [""]
  script: |
    # configure kaniko
    export KCFG=$(printf '{"auths":{"%s":{"username":"%s","password":"%s"}}}' \
      "$CI_REGISTRY" "$CI_REGISTRY_USER" "$CI_REGISTRY_PASSWORD")
    echo $KCFG > /kaniko/.docker/config.json

    /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile \
    --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG \
    --destination $CI_REGISTRY_IMAGE:latest

Listing 4-4GitLab CI pipeline configuration

新的.gitlab-ci.yml配置文件包含一个名为tag_build的作业。作业名称由用户定义,可以包含任何描述性文本。GitLab CI 流水线有三个默认阶段:构建、测试和部署。tag_build作业被分配了 stage、build,并且仅在ds/jupyterlab-apk8s项目接收到推送到该项目的新标签时运行。

GitLab CI/CD 流水线配置,如清单 4-4 所示,通过规则和脚本为 GitLab 运行者提供构建和部署指令。有关流水线配置的详细信息,请参考 GitLab 的官方文档。 23

加尼科

从 Docker 容器中构建容器映像需要安装主机服务器的 Docker 套接字。这种方法有相当大的安全隐患,使用 GitLab CI 和 Kubernetes 等自动化 CI/CD 解决方案很难实现。为了解决这个问题,Google 开发了 Kaniko, 24 项目,专门设计为“在容器或 Kubernetes 集群中从 docker 文件构建容器映像的工具”

tag_build作业使用 Kaniko 映像。GitLab CI 检查指定的git标签并运行/kaniko/executor,指定Dockerfile。在执行程序被调用之前,Kaniko 配置 JSON 文件在/kaniko/.docker/config.json生成,向ds/jupyterlab-apk8s项目的容器注册中心提供认证凭证。

综合环境变量

GitLab CI 为作业中指定的映像容器提供了一组广泛的环境变量 25tag_build作业使用环境变量CI_REGISTRYCI_REGISTRY_USERCI_REGISTRY_PASSWORD为 Kaniko 提供ds/jupyterlab-apk8s项目的容器注册中心的认证凭证。变量CI_REGISTRY_IMAGE是基于项目的映像的路径——在本例中是reg.gitlab.apk8s.dev/ds/jupyterlab-apk8s。最后,CI_COMMIT_TAG是触发该作业的git标签,用于与标签latest一起标记容器。

运行流水线

一旦标签被提交到包含文件.gitlab-ci.ymlds/jupyterlab-apk8s存储库,GitLab CI 流水线就会被触发。标记提交,如下所示:

git commit v0.0.1

推送新标签:

git push origin v0.0.1

最后,打开网络浏览器至<gitlab>/ds/jupyterlab-apk8s/pipelines(见图 4-9 )。点击流水线运行状态查看作业。

img/483120_1_En_4_Fig9_HTML.jpg

图 4-9

GitLab 我们的流水线

流水线详图(见图 4-10 )显示了.gitlab-ci.yml配置文件中定义的作业tag_build。容器工作流的最佳实践将涉及测试阶段的工作,包括功能测试和容器安全检查。

Container security

有大量专门介绍 CI/CD 和容器安全的书籍和资源。尽管超出了本书的范围,CoreOS 的 Clair 26 等工具通过静态分析检查容器漏洞。纤毛, 27 也通过 CoreOS,保证了网络的连通性。这两个项目都有很好的文档记录,并且支持 Kubernetes 部署。

img/483120_1_En_4_Fig10_HTML.jpg

图 4-10

GitLab CI 流水线作业

接下来,点击tag_build任务,在 web 终端中查看其流程,如图 4-11 所示。jupyterlab-apk8s映像包含大量的依赖项,可能需要 20 到 30 分钟来构建。

img/483120_1_En_4_Fig11_HTML.jpg

图 4-11

GitLab CI 运行作业

构建完成后,在数据科学组项目 jupyterlab-apk8sreg.gitlab.apk8s.dev/ds/jupyterlab-apk8s注册表中会有一个新的映像,标签为v0.0.1latest,如图 4-12 所示。

img/483120_1_En_4_Fig12_HTML.jpg

图 4-12

GitLab 容器注册表

Kubernetes 中的手动测试

前面几节描述了一个名为 Data Science (ds)的 GitLab 组和项目 jupyterlab-apk8s 的创建,以及一个 GitLab CI 配置,该配置构建了一个定制的 Jupyter 笔记本容器,现在在reg.gitlab.apk8s.dev/ds/jupyterlab-apk8s:v0.0.1可用。

新的 Jupyter 笔记本旨在由 JupyterHub 进行配置,稍后将在数据科学一章中介绍。在 dev2 集群上手动测试新的jupyterlab-apk8s映像可以通过使用kubectl使用几个命令来完成。

jupyterlab-apk8s项目的 GitLab 容器注册是私有的,从 dev2 集群对其进行拉取需要带有访问令牌的 Kubernetes 秘密。通过单击左侧导航栏中的设置,选择存储库,并单击部署令牌部分中的扩展按钮,为jupyterlab-apk8s GitLab 项目注册表创建一个访问令牌。

创建一个名为 k8s 的令牌,跳过字段到期,并检查作用域中的 read_registry (见图 4-13 )。

img/483120_1_En_4_Fig13_HTML.jpg

图 4-13

GitLab 部署令牌

准备名称空间

创建名称空间notebook-testing(在 dev2 集群中),缩进以测试定制笔记本:

kubectl create namespace notebook-testing

用 GitLab 生成的密钥和值添加一个docker-registry Secret。图 2-13 显示了一个生成的令牌,带有密钥gitlab+deploy-token-3和值W8x6_MxWxMYKRTfhMstU。运行以下命令,在notebook-testing名称空间中创建新的密码(相应地更改用户名、密码、服务器和电子邮件):

kubectl create secret docker-registry \
    ds-jupyterlab-apk8s-regcred \
    --namespace=notebook-testing \
    --docker-server=reg.gitlab.apk8s.dev \
    --docker-username=gitlab+deploy-token-3 \
    --docker-password=W8x6_MxWxMYKRTfhMstU \
    --docker-email=cjimti@gmail.com

接下来,用新的 docker-registry 秘密更新名称空间的默认 Kubernetes 服务帐户。Kubernetes 使用默认服务帐户的docker-registry秘密向 GitLab 容器注册中心进行认证。

使用以下命令编辑现有的默认服务帐户:

kubectl edit sa default -n notebook-testing

添加清单 4-5 中的最后两行并保存。

Note

另一种方法是将当前的服务帐户清单保存到文件kubectl get sa default -n notebook-testing -o yaml > ./sa.yaml,编辑sa.yaml,并使用kubectl apply -f sa.yml重新应用。

#...
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2019-08-01T20:18:34Z"
  name: default
  namespace: notebook-testing
  resourceVersion: "21353"
  selfLink: /api/v1/namespaces/notebook-testing/serviceaccounts/default
  uid: 2240fc34-b050-4ccb-9f96-f4f378842dbd
secrets:
- name: default-token-dhj94
imagePullSecrets:
- name: ds-jupyterlab-apk8s-regcred

Listing 4-5Editing the notebook-testing default Service Account

运行笔记本

上一节配置了notebook-testing名称空间中的默认服务帐户,该帐户能够根据ds/jupyterlab-apk8s项目提供的 GitLab 注册表进行身份验证。运行容器进行测试意味着几乎不需要保存和版本化声明性配置。Kubectl 提供了一个命令性的kubectl run 28 命令,该命令可以生成一个最小的 Pod 配置,用于临时任务,比如测试、调试、实验或者仅仅是演示功能。

下面的kubectl命令启动一个名为test-notebook的 Pod。当完成时,Pod 被设置为移除其配置,并且永远不会通过标志--rm=true--restart=Never自动重启。环境变量JUPYTER_ENABLE_LAB=yes通知 Jupyter 笔记本在更新后的 JupyterLab 模式下启动。

发出以下命令,观察类似图 4-14 的输出:

img/483120_1_En_4_Fig14_HTML.jpg

图 4-14

运行自定义 Jupyter 笔记本电脑

kubectl run -i -t test-notebook \
  --namespace=notebook-testing \
  --restart=Never --rm=true \
    --env="JUPYTER_ENABLE_LAB=yes" \
  --image=reg.gitlab.apk8s.dev/ds/jupyterlab-apk8s:v0.0.1

容器jupyterlab-apk8s很大,根据网络条件,可能需要几分钟来下载、创建和启动 Pod。一旦 Pod 运行,从输出日志中复制生成的令牌,如图 4-14 所示。

默认情况下,Jupyter 笔记本监听端口 8888。在这样的测试和演示中,通常将 Pod 容器直接转发到本地工作站,而不是配置服务和入口。

Caution

Jupyter 笔记本有意并有目的地允许远程代码执行。将 Jupyter 笔记本暴露给公共接口需要适当的安全考虑,在“索引和分析”和“AIML 平台化”章节中会进一步讨论。

使用以下命令将test-notebook Pod 端口转发:

kubectl port-forward test-notebook 8888:8888 \
-n notebook-testing

一旦 kubectl 开始将本地工作站上的端口 8888 转发到 Pod 中的端口 8888,使用容器输出中提供的令牌(如图 4-14 所示)在 web 浏览器中打开jupyterlab-apk8s。浏览至

http://localhost:8888/?token=3db...1e5

新的 Jupyter 笔记本的加载和操作应该与本章前面的本地测试一样(见图 4-6 ),只是这次是在 dev2 Kubernetes 集群中。下一节将介绍如何使用 GitLab API 用户令牌从笔记本存储库中提取和推送文件。

知识库访问

各种机器学习自动化和无服务器平台使用 CI/CD 组件以及 VCS(版本控制系统)库来构建和部署容器化的工作负载。下面是一个简单的方法来演示一个源自集群的 CI/CD 流水线,在这个例子中,从jupyterlab-apk8s运行容器中修改jupyterlab-apk8s映像。

用户设置中创建一个 GitLab 个人访问令牌(可以在用户头像下的下拉菜单中找到)。令牌名称仅用于参考。检查 api 范围,点击“创建个人访问令牌”(见图 4-15 )。记录生成的令牌值,因为这不会再次显示;但是,用户可以随时生成新令牌。

img/483120_1_En_4_Fig15_HTML.jpg

图 4-15

GitLab 部署令牌

在网络浏览器中返回到运行自定义 Jupyter 笔记本jupyterlab-apk8stest-notebook窗格。在启动器选项卡中,在标题为其他的部分下,选择终端。一旦命令提示符可用,克隆jupyterlab-apk8sand的源代码,用之前生成的令牌替换它:

export GL_TOKEN=JDYxbywCZqS_N8zAsB6z
export GL_PROJ=gitlab.apk8s.dev/ds/jupyterlab-apk8s.git
git clone https://oauth2:$GL_TOKEN@$GL_PROJ

Caution

和其他开发者分享这个跑步笔记本,暴露了你的个人 GitLab 令牌。“索引和分析”和“AIML 平台化”这两章演示了笔记本和存储库访问的替代方法。

在本书的后面,“平台化区块链”一章使用 Python 包web3py-solc从用户提供的 Jupyter 笔记本电脑中与集群内以太坊区块链网络进行通信。“流水线”一章使用 Python 包paho-mqtt开发测试 MQTT 客户机和事件监听器。将这些包添加到自定义的 JupyterLab 容器中可以确保开发人员始终可以使用一组标准的依赖项。额外的包很容易在运行时添加。

Note

jupyterlab-apk8s容器很大,包含许多可能不是所有开发人员都使用的包和应用。开发者也可能需要额外的包。本书中定义的jupyterlab-apk8s容器是为了演示的目的。一个可选的实践包括创建一套侧重于特定开发领域的基本定制笔记本。

接下来,打开克隆项目中的 docker 文件,编辑包含pip install命令的RUN层。添加 Python 包paho-mqttweb3py-solc,如清单 4-5 所示。

# Installs blockchain, data science, and machine learning
# python packages
RUN pip install --no-cache \
    rubix \
    python-gitlab \
    scipy \
    numpy \
    pandas \
    scikit-learn \
    matplotlib \
    tensorflow \
    torch \
    torchvision \
    fastai \
    octave_kernel \
    jupyterlab-git \
    paho-mqtt \
    web3 \
    py-solc

Listing 4-5Source fragment from Dockerfile

最后,提交更改,标记 commit v0.1.2,并将其推回远程:

git commit -a -m "Blockchain and IoT packages."
git tag v0.1.2
git push origin v0.1.2

在本章的前面,项目ds/jupyterlab-apk8s被配置为在新标签被推送到存储库时触发 GitLab CI 流水线。一旦 CI 流水线完成,容器reg.gitlab.apk8s.dev/ds/jupyterlab-apk8s:v0.1.2将变得可用。

GitOps

GitOps, 29 是 Weaveworks 推广的一个流程, 30 是 Kubernetes CI/CD 范围内的另一个潮流概念。GitOps 包括应用对git push事件的反应。GitOps 主要关注与 Git 存储库中的配置所描述的状态相匹配的 Kubernetes 集群。简单来说,GitOps 的目标是用git push取代kubectl apply。广受欢迎和支持的 GitOps 实现包括 ArgoCD、 31 Flux、 32 和 Jenkins X. 33

摘要

本章从较高的层面介绍了 CI/CD 概念,以及从开发和运营的角度来看它们的适用范围。使用在 Vultr 运行的单节点(k3s) Kubernetes 集群和在 Scaleway 运行的四节点定制 Kubernetes 集群展示了应用(平台)级别的多集群、多云协调。运行多个责任划分明确的小型集群是一种新兴的架构模式。本章开发了一个定制的 Jupyter 笔记本,使用 GitLab CI 流水线构建,并被手动部署和用于进一步开发其功能。

本章没有涉及部署。部署通常被定义为 CI/CD 流程中的最后一步,并且与特定的应用及其生产需求高度相关。

本章的总体目标是将 CI/CD(以一种简单的方式)作为一个平台特性来呈现,展示扩展到独立开发关注点之外的机会,并将这种强大的机制集成到企业应用平台的核心中。新的软件平台是可扩展的,容器化的工作负载是扩展在 Kubernetes 上开发的平台的自然手段。在 Kubernetes 生态系统中,开发和部署容器都是可能的。正如本章所展示的,提供一个可以从内部无限扩展的平台是可能的。

下一章将介绍数据,特别是进入数据库、索引、事件队列和文件系统的数据流水线。数据是本书中介绍的平台的核心,从数据的收集、分析、处理和呈现到从人工智能的推理或物联网事件触发的智能合同上的区块链交易结果中产生的新数据。接下来的章节打算从平台的角度开始处理数据。

Footnotes 1

https://jupyterlab.readthedocs.io

  2

https://jupyterhub.readthedocs.io

  3

www.scaleway.com

  4

https://gitlab.apk8s.dev/help/user/project/clusters/index#security-implications

  5

https://docs.gitlab.com/runner/

  6

https://docs.gitlab.com/ce/ci/

  7

https://docs.gitlab.com/ee/topics/autodevops/

  8

https://knative.dev/

  9

https://helm.sh/docs/glossary/#tiller

  10

https://jupyterlab.readthedocs.io/en/stable/user/notebook.html

  11

https://github.com/jupyter/docker-stacks/tree/master/minimal-notebook

  12

www.gnu.org/software/octave/

  13

www.mathworks.com/products/matlab.html

  14

https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html

  15

https://github.com/txn2/kubefwd

  16

www.coursera.org/learn/machine-learning

  17

www.udacity.com/course/ai-programming-python-nanodegree--nd089

  18

www.fast.ai/about/#jeremy

  19

http://course18.fast.ai/ml

  20

https://jupyterlab.readthedocs.io/en/stable/user/extensions.html

  21

https://blog.jupyter.org/99-ways-to-extend-the-jupyter-ecosystem-11e5dab7c54

  22

https://docs.gitlab.com/12.1/ce/workflow/repository_mirroring.html

  23

https://docs.gitlab.com/ee/ci/yaml/

  24

https://github.com/GoogleContainerTools/kaniko

  25

https://docs.gitlab.com/ce/ci/variables/

  26

https://github.com/coreos/clair

  27

https://github.com/cilium/cilium

  28

https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#run

  29

www.gitops.tech/

  30

www.weave.works/

  31

https://argoproj.github.io/argo-cd/

  32

https://github.com/fluxcd/flux

  33

https://jenkins-x.io/

  34

https://content.pivotal.io/blog/kubernetes-one-cluster-or-many

 

五、流水线

所有的软件平台都是通过一种或另一种形式的数据通信来运行的。数据驱动服务使用数据来确定流程(或逻辑),而事件驱动服务侦听事件来执行预定的流程和逻辑。从另一个意义上来说,这些服务是在传递数据,而数据的传输本身就是一个事件。数据驱动与事件驱动的架构通常归结为它们处理的数据的意义和价值,以及消费数据的服务如何操作、转换、处理或分析数据。

管理数据的应用通常是软件平台的核心组件。如何处理和维护数据通常由业务需求驱动的业务逻辑决定。一个成熟的软件平台应该提供高效灵活的应用逻辑,能够适应一系列数据管理需求,支持数据驱动和事件驱动的实现。

物联网、机器学习和区块链技术产生和消费数据,每种技术都有特定的需求:物联网设备在交流状态的同时产生和消费大量实时数据。机器学习通常需要大量的、有组织的数据目录。最后,区块链以数据的形式消费和发射事件。

本书的上下文和手头的平台认为数据尽可能通用,主要关注收集和分发,将数据的意义和价值留给更高级别的应用需求。本章重点介绍如何通过数据流水线将数据从发布服务器移动到订阅服务器。

国家和 Kubernetes

数据库管理系统(数据库)是有状态应用。传统的整体架构通常围绕一个跨多个域共享的中央数据库,而微服务架构则倾向于多个数据库,这些数据库被隔离到特定的服务和域。不管整个系统架构如何,数据库应用都是有状态的,并且不是所有有状态的应用都能轻松利用 Kubernetes 的优势。

Kubernetes 的容器化和容器编排非常适合构建由独立的、可移植的和可伸缩的组件组成的可扩展系统。所有 Kubernetes 平台的核心是 Pods,即最好被视为短暂和无状态的容器化工作负载。pod 预计会来来去去,根据规则扩展和缩减,或者在底层基础架构出现故障或进行维护时意外终止和重新生成。设计以这种方式运行的组件实现了 Kubernetes 强大的可伸缩性、性能、冗余和自修复能力的优势。在这种架构中操作有状态的应用可能很有挑战性。然而,许多应用可以在分布式体系结构上维护状态;这些应用非常适合 Kubernetes。

Kubernetes 适合通过无状态 pod 支持的庞大服务网络来交流数据和事件。然而,数据的存储和检索是有状态的活动,这个概念不太适合 Kubernetes 的短暂工作负载。移除或更换 Pod 后,Pod 中存储的数据会丢失。pod 可以连接到外部永久卷;然而,在多个 pod 之间共享持久卷并没有得到存储提供商的广泛支持,并且难以实现可接受的性能和稳定性。最后,在单个 Pod 中操作有状态的应用既不可伸缩也不容错。与 Kubernetes 配合良好的有状态应用(如数据库)通过分布式节点网络维护状态。

Kubernetes 以 StatefulSets 的形式提供有状态功能。StatefulSets 基本上等同于部署,但是部署枚举的 pod 能够通过卷声明模板重新附加到分配给它们的存储。StatefulSet 创建以序号索引命名的 pod,例如PODNAME-0..n,并提供稳定的网络 ID,例如PODNAME-4.somenamespace.svc.cluster.local。如果 StatefulSet 中的一个单元崩溃、升级或重新安排到另一个节点,Kubernetes 会创建一个具有相同名称和网络 ID 的新单元,并重新挂接以前与该单元名称关联的任何持久性卷。即使它们的工作负载是短暂的,但通过持久的命名和存储,pod 在概念上是有状态的。Elasticsearch、Cassandra 和 Kafka 是数据管理应用的一些例子,当部署在多个有状态的 pod 中时,它们工作得很好。像这样的系统管理它们的数据复制、分布式处理,并在其自我管理的集群网络中处理故障或缺失的节点。正确配置后,这些应用会在缺少一个节点(在本例中为一个单元)时继续执行,并且通常会提供无限的水平扩展。在某些方面,Kubernetes 使扩展和管理这些应用比传统方法更容易,传统方法涉及配置虚拟机或裸机服务器的繁琐任务。相比之下,Kubernetes 只需编辑和重新应用配置,就能增加所需的 pod 数量。

实时数据体系结构

旨在提供机器学习、物联网和区块链功能的软件平台需要收集、转换、处理和管理数据、元数据和指标的能力。接下来的几章将介绍一些能够在 Kubernetes 集群中运行的企业级应用,提供实时数据收集、路由、转换、索引以及数据和指标的管理(参见图 5-1 )。

img/483120_1_En_5_Fig1_HTML.jpg

图 5-1

数据管理架构

以下部分涵盖了本章中用于组装实时分布式流平台的技术,该平台可用于任何形式的入站数据,并且是为物联网专门定制的。

消息和事件队列

Web 和移动应用通常在有限的一组已定义的数据结构上运行;自定义 API 端点在结构化数据库中验证、验证和持久化这些对象。相比之下,物联网平台通常需要能够接受各种互联设备产生的各种数据类型和结构。物联网平台主要负责向其他设备和服务分发设备状态。

发布和订阅(也称为发布/订阅)应用解决了收集和分发数据(通常称为事件或消息)的问题,并将几乎无限数量的生产者与几乎任意数量的感兴趣的消费者联系起来。本章重点介绍如何为所有实时数据和事件分发实现高度可用的分布式流媒体平台 Apache Kafka,以及为物联网专门构建的 MQTT 代理 Mosquitto。

分布式流媒体平台

Apache Kafka 将自己标榜为分布式流媒体平台,并充当平台内实时数据操作的一种“中枢神经系统”。Kafka 可以通过六个节点和适当的配置每秒处理数十万条消息,这一能力远远超过了以数据为中心的大型企业之外的大多数用例。Kafka 将消息(数据)记录为主题内的记录,由键、值和时间戳组成。Kafka 通过其消费者、生产者、流和连接器 API 管理记录的关系、收集和分发。

Kafka 为所有主流编程语言提供了稳定成熟的客户端库,允许定制平台组件实时操作数据(事件和消息)。

MQTT 和物联网

MQTT 1 (消息队列遥测传输)是一种发布/订阅消息协议,专为物联网和 IIoT(工业物联网)实现而设计。设备可以通过 MQTT 代理发布和订阅主题,代理可以被桥接在一起。MQTT 被设计成轻量级的,能够在资源受限的环境中运行,包括 Raspberry Pi。22

考虑一个组织,该组织有一个工厂,操作数千个传感器和控制器,直接与内部 MQTT 代理通信。该组织还订阅了一个云托管的 MQTT 解决方案,用于从远程或隔离的设备收集指标。如图 5-2 所示,这些代理中的每一个都可以桥接在一起,并与一个更大的数据平台进行双向通信。

img/483120_1_En_5_Fig2_HTML.jpg

图 5-2

MQTT 网络

Mosquitto 是一个流行的开源 MQTT 代理,本章稍后将为集群内 MQTT 操作进行配置。Mosquitto 可以发布和订阅任何其他 MQTT 兼容的代理,通常是本地或基于云的 SaaS。

发展环境

本章使用第四章中相同的、廉价的 Scaleway 四节点集群设置。该集群包括一个用于 Kubernetes 主节点的 DEV1-M (3 个 CPU/4G RAM/40G SSD)和三个用于工作节点的 DEV1-L (4 个 CPU/8G RAM/80GB SSD)。配置和操作 Apache Kafka、Apache NiFi、Elasticsearch 和 Logstash 利用了这个小型开发集群的大部分 CPU 和 RAM。这些规格是绝对的最低要求,应该根据需要进行扩展。

Note

本章中使用的整个三节点开发集群相当于一个 12 核 CPU/32GB RAM 服务器实例,类似于典型企业配置中的单个生产节点。

集群范围的配置

本章使用与第三章中详述的相同的通用 Kubernetes 配置,包括入口 Nginx、证书管理器、Rook-Ceph 和监控配置。如果遵循前面的章节,从清单 3-15cluster-apk8s-dev3/000-cluster中的目录cluster-apk8s-dev1/000-cluster复制并应用配置清单,并创建目录cluster-apk8s-dev3/003-data(如清单 5-1 所示)来保存本章使用的清单。

.
└── cluster-apk8s-dev1
└── cluster-apk8s-dev2
└── cluster-apk8s-dev3
   └── 000-cluster
      ├── 00-ingress-nginx
      ├── 10-cert-manager
      ├── 20-rook-ceph
      └── 30-monitoring
   └── 003-data

Listing 5-1Development Cluster configuration layout

Note

以数字为前缀的目录是推断优先顺序的一个简单约定。配置清单通常可以以任何顺序应用。

数据命名空间

创建目录cluster-apk8s-dev3/003-data/000-namespace来包含名称空间范围的配置清单。考虑到其规模和演示用途,此开发集群应被视为单租户。然而,在基于租户的名称空间(例如clientx-data)中包含所有与数据相关的功能,允许应用细粒度的基于角色的访问控制和网络规则。因为这个开发集群只有一个租户,所以名称空间data是合适的。以下配置适用于新数据命名空间中的所有服务。

cluster-apk8s-dev3/003-data/000-namespace目录中,在清单 5-2 中的一个名为00-namespace.yml的文件中创建一个 Kubernetes 名称空间。

apiVersion: v1
kind: Namespace
metadata:
  name: data

Listing 5-2Data Namespace

应用命名空间配置:

$ kubectl apply -f 00-namespace.yml

TLS 证书

在本章的后面,安全入口配置应用于nifi.data.dev3.apk8s.dev的 NiFi 和kib.data.dev3.apk8s.dev的 Kibana,提供对其用户界面的外部访问。证书管理器和集群发行者应该出现在集群中(参见第三章)。

在清单 5-3 中的文件05-certs.yml中创建一个证书配置。

apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: data-cert
  namespace: data
spec:
  secretName: data-production-tls
  issuerRef:
    name: letsencrypt-production
    kind: ClusterIssuer
  commonName: data.dev3.apk8s.dev
  dnsNames:
    - data.dev3.apk8s.dev
    - nifi.data.dev3.apk8s.dev
    - kib.data.dev3.apk8s.dev
  acme:
    config:
      - http01:
          ingressClass: nginx
        domains:
          - data.dev3.apk8s.dev
          - nifi.data.dev3.apk8s.dev
          - kib.data.dev3.apk8s.dev

Listing 5-3Certificates for the data Namespace

应用证书配置:

$ kubectl apply -f 05-certs.yml

基本认证

开发集群使用基本认证 3 (基本认证)作为保护入口的便利方法。跨入口使用单个基本身份验证秘密简化了开发过程中身份验证的使用,并且在需要时可以用更复杂的方法(如 OAuth)来代替。

用实用程序htpasswd : 4 创建一个名为 auth 的文件

$ htpasswd -c ./auth sysop

用前面生成的auth文件创建一个名为sysop-basic-auth的通用 Kubernetes 秘密:

$ kubectl create secret generic sysop-basic-auth \
--from-file auth -n data

Apache 动物园管理员

Apache Zookeeper5已经成为许多需要分布式协作的流行应用的标准,包括 Hadoop、 6 HBase、 7 Kafka、NiFi 和 Elasticsearch。在这一章中,Kafka 和 NiFi 都使用了一个共享的 Zookeeper 集群。Elasticsearch 在这个环境中作为单个节点运行;然而,更大的弹性搜索集群也可以利用共享的动物园管理员。

Zookeeper 可以被缩放以容忍给定数量的故障节点;然而,一些架构倾向于多个独立的安装,以避免单点故障。缩放或多个 Zookeeper 配置是一个生产问题,而在开发环境中共享该服务可以更好地利用有限的资源。

创建目录cluster-apk8s-dev3/003-data/010-zookeeper。在新的010-zookeeper目录中,从清单 5-4 中创建一个名为10-service.yml的文件。

apiVersion: v1
kind: Service
metadata:
  name: zookeeper
  namespace: data
spec:
  ports:
    - name: client
      port: 2181
      protocol: TCP
      targetPort: client
  selector:
    app: zookeeper
  sessionAffinity: None
  type: ClusterIP

Listing 5-4Zookeeper Service

应用 Zookeeper 服务配置:

$ kubectl apply -f 10-service.yml

Kafka 和 NiFi 等 Zookeeper 客户端管理它们与单个节点的关系。Kubernetes 中的标准服务定义被分配了一个 IP 地址,通常被配置为将与该服务的任何通信路由到与指定选择器和端口匹配的任何 Pod。然而,像 Zookeeper 这样的应用要求每个 Zookeeper 节点(作为 Pod 运行)能够与它的对等节点(作为 Pod 运行)进行通信。标准的 Kubernetes 服务不足以在对等感知集群中使用,因为每个节点都必须能够专门与其他节点通信,而不仅仅是与选择器和端口匹配的任何节点。Kubernetes 通过无头服务的概念提供这种功能, 8 ,这是一种没有定义ClusterIP(clusterIP: None)的服务。下面的服务定义创建了一个无头服务,为匹配选择器 app 的 pod 返回 DNS 条目:zookeeper,如下一节描述的 StatefulSet 中所定义的。

在清单 5-5 中的文件10-service-headless.yml中创建一个无头服务配置。

apiVersion: v1
kind: Service
metadata:
  name: zookeeper-headless
  namespace: data
spec:
  clusterIP: None
  ports:
    - name: client
      port: 2181
      protocol: TCP
      targetPort: 2181
    - name: election
      port: 3888
      protocol: TCP
      targetPort: 3888
    - name: server
      port: 2888
      protocol: TCP
      targetPort: 2888
  selector:
    app: zookeeper
  sessionAffinity: None
  type: ClusterIP

Listing 5-5Zookeeper Headless Service

应用 Zookeeper 无头服务配置:

$ kubectl apply -f 10-service-headless.yml

接下来,在清单 5-6 中的文件40-statefulset.yml中创建一个 StatefulSet 配置。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zookeeper
  namespace: data
spec:
  podManagementPolicy: OrderedReady
  replicas: 2
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: zookeeper
  serviceName: zookeeper-headless
  template:
    metadata:
      labels:
        app: zookeeper
    spec:
      containers:
        - command:
            - /bin/bash
            - -xec
            - zkGenConfig.sh && exec zkServer.sh start-foreground
          env:
            - name: ZK_REPLICAS
              value: "2"
            - name: JMXAUTH
              value: "false"
            - name: JMXDISABLE
              value: "false"
            - name: JMXPORT
              value: "1099"
            - name: JMXSSL
              value: "false"
            - name: ZK_CLIENT_PORT
              value: "2181"
            - name: ZK_ELECTION_PORT
              value: "3888"
            - name: ZK_HEAP_SIZE
              value: 1G
            - name: ZK_INIT_LIMIT
              value: "5"
            - name: ZK_LOG_LEVEL
              value: INFO
            - name: ZK_MAX_CLIENT_CNXNS
              value: "60"
            - name: ZK_MAX_SESSION_TIMEOUT
              value: "40000"
            - name: ZK_MIN_SESSION_TIMEOUT
              value: "4000"
            - name: ZK_PURGE_INTERVAL

              value: "0"
            - name: ZK_SERVER_PORT
              value: "2888"
            - name: ZK_SNAP_RETAIN_COUNT
              value: "3"
            - name: ZK_SYNC_LIMIT
              value: "10"
            - name: ZK_TICK_TIME
              value: "2000"
          image: gcr.io/google_samples/k8szk:v3
          imagePullPolicy: IfNotPresent
          livenessProbe:
            exec:
              command:
                - zkOk.sh
            failureThreshold: 3
            initialDelaySeconds: 20
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          name: zookeeper
          ports:
            - containerPort: 2181
              name: client
              protocol: TCP
            - containerPort: 3888
              name: election
              protocol: TCP
            - containerPort: 2888
              name: server
              protocol: TCP
          readinessProbe:
            exec:
              command:

                - zkOk.sh
            failureThreshold: 3
            initialDelaySeconds: 20
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /var/lib/zookeeper
              name: data
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext:
        fsGroup: 1000
        runAsUser: 1000
      terminationGracePeriodSeconds: 30
      volumes:
        - emptyDir: {}
          name: data
  updateStrategy:
    type: OnDelete

Listing 5-6Zookeeper StatefulSet

最后,应用 Zookeeper StatefulSet 配置:

$ kubectl apply -f 40-statefulset.yml

Zookeeper 是 Kafka 和 NiFi 的共享依赖项,现在可以在开发集群上使用。下一节将建立一个双节点 Apache Kafka 集群。

ApacheKafka

Apache Kafka”用于构建实时数据流水线和流媒体应用。它具有横向可伸缩性、容错性、极快的速度,并在数千家公司的生产中运行。” 9 以下配置建立了一个双节点 Kafka 集群,非常适合小规模开发环境和数据科学活动。如前所述,Apache Kafka 是这个数据驱动平台的中枢神经系统。除了在每一种主流编程语言中提供稳定且功能丰富的客户端库之外,许多数据管理应用还开发了一流的连接器来发布和订阅 Kafka 事件,包括 Logstash 和 NiFi,这将在本章的后面进行演示;见图 5-3 。

img/483120_1_En_5_Fig3_HTML.jpg

图 5-3

ApacheKafka

Apache Kafka 等高性能、低延迟事件队列在高度优化的专用裸机服务器上以最高效率运行。在容器中,在具有抽象存储和覆盖网络的共享虚拟实例上运行 Kafka(如本书中定义的 Kubernetes 集群),会显著降低其效率和吞吐量。然而,在某些情况下,最佳性能的降低可能不会被注意到,或者可能很容易通过缩放来补偿。在 Kubernetes 中运行 Kafka 带来了许多优势,包括统一网络、DNS、可伸缩性、自修复、安全性、监控以及与其他组件的统一控制面板。在概念层面上,Kafka 是构成本书所描述的基于 Kubernetes 的数据平台的众多组件之一。虽然 Kafka 本身可能不会从 Kubernetes 内部的管理中获得大量利润,但更大的数据平台通过其基本组件之间更高的凝聚力而受益于 Kafka 的包容性。

以下配置以类似于 Zookeeper(在上一节中配置)的方式设置 Kafka,并添加了持久卷,如图 5-4 所示。

img/483120_1_En_5_Fig4_HTML.png

图 5-4

ApacheKafka 和动物园管理员 Kubernetes 配置

创建目录cluster-apk8s-dev3/003-data/020-kafka。在新的020-kafka目录中,从清单 5-7 中创建一个名为10-service.yml的文件。

apiVersion: v1
kind: Service
metadata:
  name: kafka
  namespace: data
spec:
  ports:
    - name: broker
      port: 9092
      protocol: TCP
      targetPort: kafka
  selector:
    app: kafka
  sessionAffinity: None
  type: ClusterIP

Listing 5-7Kafka Service

应用 Kafka 服务配置:

$ kubectl apply -f 10-service.yml

接下来,在清单 5-8 中名为10-service-headless.yml的文件中为 Kafka 创建一个无头服务配置。

apiVersion: v1
kind: Service
metadata:
  name: kafka-headless
  namespace: data
spec:
  clusterIP: None
  ports:
    - name: broker
      port: 9092
      protocol: TCP
      targetPort: 9092
  selector:
    app: kafka
  sessionAffinity: None
  type: ClusterIP

Listing 5-8Kafka Headless Service

应用 Kafka Headless 服务配置:

$ kubectl apply -f 10-service-headless.yml

接下来,在清单 5-9 中名为40-statefulset.yml的文件中为 Kafka 创建一个 StatefulSet 配置。

以下配置使用由 Confluent Inc. 10 维护的 Kafka 容器 Confluent 为其围绕 Kafka 构建的开源事件流平台提供商业支持。本书中使用的 Kafka 功能可以与 Confluent 的 Kafka 发行版和标准的上游 Apache Kafka 一起使用。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: kafka
  name: kafka
  namespace: data
spec:
  podManagementPolicy: OrderedReady
  replicas: 2
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: kafka
  serviceName: kafka-headless
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
        - command:
            - sh
            - -exc

            - |
              unset KAFKA_PORT && \
              export KAFKA_BROKER_ID=${HOSTNAME##*-} && \
              export KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://${POD_IP}:9092 && \
              exec /etc/confluent/docker/run
          env:
            - name: POD_IP
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: status.podIP
            - name: KAFKA_HEAP_OPTS
              value: -Xmx1G -Xms1G
            - name: KAFKA_ZOOKEEPER_CONNECT
              value: zookeeper-headless:2181
            - name: KAFKA_LOG_DIRS
              value: /opt/kafka/data/logs
            - name: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR
              value: "1"
            - name: KAFKA_JMX_PORT
              value: "5555"
          image: confluentinc/cp-kafka:5.3.1-1
          imagePullPolicy: IfNotPresent
          livenessProbe:
            exec:
              command:
                - sh
                - -ec
                - /usr/bin/jps | /bin/grep -q SupportedKafka

            failureThreshold: 3
            initialDelaySeconds: 30
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 5
          name: kafka-broker
          ports:
            - containerPort: 9092
              name: kafka
              protocol: TCP
          readinessProbe:
            failureThreshold: 3
            initialDelaySeconds: 30
            periodSeconds: 10
            successThreshold: 1
            tcpSocket:
              port: kafka
            timeoutSeconds: 5
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /opt/kafka/data
              name: datadir
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 60
  updateStrategy:
    type: OnDelete
  volumeClaimTemplates:
    - metadata:
        name: datadir
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 5Gi
        storageClassName: rook-ceph-block

Listing 5-9Kafka StatefulSet

应用 Kafka StatefulSet 配置:

$ kubectl apply -f 40-statefulset.yml

Kubernetes Pod 中断预算 11 限制在任何给定时间允许停机的 Pod 数量,节点故障或 Pod 错误等计划外停机除外。PodDisruptionBudget 配置对于更新表示高可用性集群(如 Kafka)的有状态集特别有用。通过适当的配置和资源,Kafka 集群可以保持完全运行,而一部分节点处于离线状态。

Note

由于本章中指定的开发环境的资源有限,下面定义的 Kafka 配置是稳定的,但不是高度可用的。 12

在清单 5-10 中名为45-pdb.yml的文件中为 Kafka 创建一个 PodDisruptionBudget 配置。

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: kafka
  namespace: data
  labels:
    app: kafka
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: kafka

Listing 5-10Kafka Pod Disruption Budget

最后,应用 Kafka PodDisruptionBudget 配置:

$ kubectl apply -f 45-pdb.yml

新的开发环境现在运行一个双节点 Kafka 集群。下一节将为测试和调试设置 Pod。

Kafka 客户端实用程序窗格

Kafka 可以用最少的管理开销在这个数据驱动平台的几乎每个组件之间代理数据。但是,Confluence 提供的 Kafka 容器包含几个有用的脚本(见表 5-1 )用于测试、备份、安全配置和一般管理功能。运行 Kafka 客户端实用程序 Pod 提供了对这个关键平台组件的命令行管理访问。

Kafka 测试客户机 Pod 运行与前面配置的操作集群相同的confluentinc/cp-kafka:5.3.1-1映像。然而,Pod 被配置为执行命令tail -f /dev/null而不是标准入口点,保持tail为活动进程并阻止 Pod 完成。

在清单 5-11 中的文件99-pod-test-client.yml中创建一个 Kafka 测试客户端配置。

apiVersion: v1
kind: Pod
metadata:
  name: kafka-client-util
  namespace: data
spec:
  containers:
    - command:
        - sh
        - -c
        - exec tail -f /dev/null
      image: confluentinc/cp-kafka:5.3.1-1
      imagePullPolicy: IfNotPresent
      name: kafka
      resources: {}
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File

Listing 5-11Kafka test client Pod

应用 Kafka 测试客户端 Pod 配置:

$ kubectl apply -f 99-pod-client-util.yml

Kafka 测试客户端 Pod 中运行的容器映像confluentinc/cp-kafka:4.1.2-2附带了表 5-1 中列出的实用程序。运行表 5-1 中列出的任何命令,带有--help标志,以获得配置参数列表。Cloudera 在其网站上提供了详细的文档: Kafka 使用命令行工具管理13

表 5-1

Kafka 客户端实用程序脚本

|

脚本

|

描述

| | --- | --- | | Kafka-acls | 用户认证、授权和访问控制列表管理。 | | Kafka-broker-API-版本 | 列出集群中所有节点的 API 版本。 | | Kafka-config | 添加/删除主题、客户机、用户或代理的实体配置。 | | Kafka——控制台——消费者 | 用于创建、更改、列出和描述主题。 | | Kafka-控制台-制片人 | 从标准输出中读取数据,并将其写入 Kafka 主题。 | | Kafka-消费群体 | 列出当前的消费群体。 | | Kafka-消费者-抵销-检查 | 返回特定使用者组中每个使用者读取和写入的带有滞后时间的消息数。 | | Kafka-消费者-性能-测试 | 对代理和主题运行消费者性能测试。 | | Kafka-委托-代币 | 委托令牌是经纪人和客户之间共享的秘密。创建、描述、续订和终止令牌。 | | Kafka-删除-记录 | 从具有给定偏移量的主题中删除记录。 | | Kafka 日志-dirs | 输出一个 JSON 对象,包含每个代理的日志信息。 | | Kafka-镜子制造者 | 复制 Kafka 集群。 14 | | Kafka-首选-复制品-选举 | 重新平衡主题。 十五 | | Kafka-生产者-性能-测试 | 对代理和主题运行生产者性能测试。 | | Kafka-重新分配-分区 | 将 Kafka 主题分区领导者重新分配给不同的 Kafka 代理。 | | Kafka-重播-日志-制作人 | 消费来自一个主题的消息,并在另一个主题中重放(产生)它们。 | | Kafka-复制品-验证 | 验证一个或多个主题的数据复制是否正确。 | | Kafka 式的 | 提供直接调用 Kafka 类的能力;主要由其他脚本使用。 | | Kafka-服务器-开始 Kafka-服务器-停止 | 在管理汇合分配的情况下不使用。 | | Kafka-流-应用-重置 | 重置 Kafka Streams 16 应用的内部状态。 | | Kafka-主题 | 创建、列出、配置和删除主题。 | | Kafka-可验证的-生产者 Kafka-可验证的-消费者 | 生成并使用一定数量的消息进行测试。 |

Testing Kafka

  1. 在新的 Kafka 测试客户端 Pod 上参加一个 Bash 17 会话。

    $ kubectl exec -it kafka-client-util bash -n data
    
    
  2. 在新 Pod 的命令行中,创建主题test一个分区一个副本

    # kafka-topics --zookeeper zookeeper-headless:2181 \
    --topic test --create --partitions 1 --replication-factor
    
    
  3. 列出 Kafka 集群中的所有主题。新的集群应该只有测试主题以及一个或多个以两个下划线开头的内部主题。

    # kafka-topics --zookeeper zookeeper-headless:2181 --list
    
    
  4. 听新的测试题目。Kafka-控制台-消费者向控制台打印消息。

    # kafka-console-consumer --bootstrap-server kafka:9092 \
    --topic test
    
    
  5. 从一个单独的终端打开 Kafka test client Pod 上的一个附加 Bash 会话。

    $ kubectl exec -it kafka-client-util bash -n data
    
    
  6. 使用 kafka-console-producer 实用程序发送测试消息。

    # kafka-console-producer --broker-list kafka:9092 \
    --topic test
    
    
  7. 键入一条消息并返回。每行文本作为消息发送到主题,并显示在运行 Kafka 控制台消费者的终端中(步骤 4)。

开发环境现在运行一个双节点 Kafka 集群,已经过测试,可以接收和发送消息了。考虑用雅虎开发的 Kafka Manager、 18 添加一个管理 web 界面!。LinkedIn 开发了陋居 19 实用程序来监控消费者延迟时间,并通过 HTTP 端点提供统计数据。同样由 LinkedIn 开发的 Cruise-control、 20 ,以及由 Pinterest 开发的 doctor Kafka21自动执行动态工作负载再平衡和自修复。Kafka 拥有一个庞大且不断发展的公用事业和第三方支持生态系统。 22

Kafka 被设计为在一个巨大的规模上工作,作为一个高度可用的、容错的、分布式的、通用的发布/订阅系统,这是将它保持在数据驱动架构的中心的一个很好的理由。Kafka 可以轻松处理来自几乎任何来源的海量数据和指标,尤其是物联网设备。然而,下一节将介绍另一个名为 Mosquitto 的发布/订阅应用,它实现了专为物联网设计的 MQTT 协议。

蚊子(MQTT)

MQTT(和 AMQP)等协议专注于轻量级客户端到消息代理通信,适用于广泛且不断增长的消费和工业物联网设备。相比之下,Apache Kafka 是一个更重的事件队列实现,能够处理和持久存储大量数据。虽然这些系统在概念上是相似的,但是在本书中向数据平台添加 MQTT 功能演示了各种协议以及在它们之间交换消息的能力。

想象一个工厂,机器状态通过内部 MQTT 代理进行控制和通信;远程数据平台中的 MQTT 代理充当客户端桥梁,将这些消息转发给 Kafka。机器学习模型对 Kafka 的最后一个小时的滚动数据进行预测分析,并做出调整特定机器状态的决定,并通过 MQTT 将该决定传达回来(见图 5-5 )。

img/483120_1_En_5_Fig5_HTML.jpg

图 5-5

Apache Kafka 和 MQTT 事件队列

Mosquitto 24 是由 Eclipse Foundation25维护的开源 MQTT 代理,作为iot.eclipse.org 项目提供的众多组件之一。

创建目录cluster-apk8s-dev3/003-data/050-mqtt。在新的050-mqtt目录中,从清单 5-12 中创建一个名为10-service.yml的文件。

apiVersion: v1
kind: Service
metadata:
  name: mqtt
  namespace: data
  labels:
    app: mqtt
spec:
  selector:
    app: mqtt
  ports:
    - protocol: "TCP"
      port: 1883
      targetPort: 1883
  type: ClusterIP

Listing 5-12Mosquitto MQTT Service

应用 MQTT 服务配置:

$ kubectl apply -f 10-service.yml

接下来,在清单 5-13 中名为20-configmap.yml的文件中为 Mosquitto 创建一个配置图。这里定义的小配置文件指示服务器以用户mosquitto的身份运行,并监听端口 1883。在这个例子中没有加密或认证,因为这个开发环境中的服务器没有直接向公众公开。有关配置选项的详细列表,请参见联机文档。 26

Caution

不要在没有启用身份验证和加密的情况下将 MQTT 代理暴露给公共互联网。所有客户端都应该被信任。强烈建议使用 VPN 或配置良好的防火墙来保护远程连接。

apiVersion: v1
kind: ConfigMap
metadata:
  name: mqtt
  namespace: data
  labels:
    app: mqtt
data:
  mosquitto.conf: |-
    user mosquitto
    port 1883

Listing 5-13Mosquitto configuration ConfigMap

应用 Mosquitto 配置配置图:

$ kubectl apply -f 20-configmap.yml

接下来,在清单 5-14 中的一个名为30-deployment.yml的文件中为 Mosquitto 创建一个部署。这里定义的 Mosquitto 旨在作为单个实例运行。为生产而扩展 Mosquitto 通常涉及为特定客户或客户群提供代理。还有许多其他的开源和商业 MQTT 代理,包括 VerneMQ、 27 一个高度可用的分布式实现,以及流行的 RabbitMQ 28 支持 AMQP、 29 STOMP、 30 以及 MQTT,它们都是开源的,用 Erlang 编写。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mqtt
  namespace: data
  labels:
    app: mqtt
spec:
  replicas: 1 # keep at 1 for
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: mqtt
  template:
    metadata:
      labels:
        app: mqtt
    spec:
      volumes:
        - name: mqtt-config-volume
          configMap:
            name: mqtt
      containers:
        - name: mqtt
          image: eclipse-mosquitto:1.6.6
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: mqtt-config-volume
              mountPath: /mosquitto/config
          ports:
            - name: mqtt
              containerPort: 1883

Listing 5-14Mosquitto Deployment

应用 Mosquitto 部署:

$ kubectl apply -f 30-deployment.yml

一个 MQTT 代理安装并运行在 Kubernetes 开发集群中。诸如 MQTT.fx 31 和 mqtt-spy 32 之类的实用程序非常适合测试和调试 MQTT 代理。下面的练习使用新的 Mosquitto Pod 上的mosquitto_sub实用程序以及在本地工作站上运行的 MQTT.fx 来测试新的代理。

Testing Mosquitto

  1. 在本地工作站上下载并安装 mqtt . FX 1 . 7 . 1 版。参观 https://mqttfx.jensd.de/index.php/download

  2. 进入 Mosquitto MQTT 代理 Pod 上的 Bash 会话(使用kubectl get pods -n data找到它的名称)。

    $ kubectl exec -it mqtt-6899646f75-g65sf sh -n data
    
    
  3. 从 Mosquitto MQTT 窗格中的命令行开始监听关于dev/test主题的消息。

    # mosquitto_sub -t dev/apk8s
    
    
  4. 从本地工作站,使用 kubectl 将mqtt Kubernetes 服务移植到运行 MQTT.fx 的本地工作站。

    $ kubectl port-forward svc/mqtt 1883:1883 -n data
    
    
  5. Open MQTT.fx and select “local mosquitto” from the connect drop-down, and then click Connect as shown in Figure 5-6.

    img/483120_1_En_5_Fig6_HTML.jpg

    图 5-6

    在本地工作站上运行的 MQTT.fx 应用

  6. 提供相同的主题;mosquitto_sub是从步骤 3 开始监听,本例中为dev/apk8s。在大文本区提供消息,点击发布

注简单的消息传递也可以通过mosquitto_sub旁边的mosquitto_pub实用程序来完成。

  1. 观察从mosquitto_sub输出打印的信息。

摘要

该平台现在包括两个受欢迎的活动队列,Kafka 和 Mosquitto。Kafka 旨在作为平台的“中枢神经系统”,负责与其他平台组件之间的状态、指标和数据的实时通信。Mosquitto 为流行的物联网通信协议 MQTT 提供支持。清单 5-15 显示了本章中开发的配置清单的概述。

.
└── cluster-apk8s-dev3
    ├── 000-cluster
    └── 003-data
        ├── 000-namespace
  │   ├── 00-namespace.yml
  │   └── 05-certs.yml
        ├── 010-zookeeper
  │   ├── 10-service-headless.yml
  │   ├── 10-service.yml
  │   └── 40-statefulset.yml
  ├── 020-kafka
  │   ├── 10-service-headless.yml
  │   ├── 10-service.yml
  │   ├── 40-statefulset.yml
  │   ├── 45-pdb.yml
  │   └── 99-pod-test-client.yml
        └── 050-mqtt
      ├── 10-service.yml
      ├── 20-configmap.yml
      └── 30-deployment.yml

Listing 5-15Data Pipeline Development Cluster configuration layout

接下来的章节将演示索引、分析、可视化、仓储和路由流经本章中配置的队列的数据的方法。

Footnotes 1

https://mqtt.org

  2

https://appcodelabs.com/introduction-to-iot-build-an-mqtt-server-using-raspberry-pi

  3

https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication

  4

https://httpd.apache.org/docs/2.4/programs/htpasswd.html

  5

https://zookeeper.apache.org

  6

https://hadoop.apache.org/

  7

https://hbase.apache.org

  8

https://kubernetes.io/docs/concepts/services-networking/service/#headless-services

  9

https://kafka.apache.org

  10

https://www.confluent.io

  11

https://kubernetes.io/docs/concepts/workloads/pods/disruptions/#how-disruption-budgets-work

  12

www.loudera.com/documentation/kafka/latest/topics/kafka_ha.html

  13

www.cloudera.com/documentation/enterprise/latest/topics/kafka_admin_cli.html

  14

https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27846330

  15

https://blog.imaginea.com/how-to-rebalance-topics-in-kafka-cluster/

  16

https://kafka.apache.org/documentation/streams/

  17

www.gnu.org/software/bash/

  18

https://github.com/yahoo/kafka-manager

  19

https://github.com/linkedin/Burrow

  20

https://github.com/linkedin/cruise-control

  21

https://github.com/pinterest/doctorkafka

  22

https://cwiki.apache.org/confluence/display/KAFKA/Ecosystem

  23

https://engineering.linkedin.com/kafka/benchmarking-apache-kafka-2-million-writes-second-three-cheap-machines

  24

https://mosquitto.org/

  25

https://eclipse.org/

  26

https://mosquitto.org/man/mosquitto-conf-5.html

  27

https://vernemq.com

  28

www.rabbitmq.com

  29

www.amqp.org

  30

https://stomp.github.io

  31

https://mqttfx.jensd.de

  32

http://kamilfb.github.io/mqtt-spy/

 

五、索引和分析

Kubernetes 的现代分布式数据平台远远超出了数据的收集、存储和传输。搜索、索引、分析和数据科学应用是以数据为中心的平台的基本要素。本章重点关注网络规模的 1 技术,这是一个在大数据主流关注之外成熟的生态系统。 2 在大数据假设有限数量的同时请求处理近乎无限的数据湖 3 的情况下,网络规模假设最终无限的同时数据需求。网络规模的分析和大数据正变得越来越紧密,并在组合能力方面快速发展。本章涵盖了为 web 级架构构建的一般数据索引、指标、分析和数据科学。下一章将介绍 Kubernetes 如何通过支持现代数据湖和数据仓库的发展来融入大数据环境。Kubernetes 构建健壮的分布式集群的能力带来了在这两个不同问题领域(web 级的大数据)的数据和控制面板之间架起桥梁的机会。

本章重点介绍了一些应用,这些应用涵盖了通信、索引和数据科学要求的一个通用而有效的范围。专注于特定问题领域的应用可能会受益于更高级别的技术;然而,一般搜索、分析和数据科学技术通常是开发集中数据驱动解决方案的多层方法的基础。

搜索和分析

数据以结构化、半结构化和非结构化的形式存在。非结构化数据包括各种数据类型,如映像、视频、音频、pdf 和原始文本。下一章将介绍如何开发数据湖来存储非结构化数据。然而,本章关注的是由 Elasticsearch 支持的半结构化、基于文档的数据,elastic search 是一种基于文档的数据索引,能够存储、检索和分析数十亿条记录。

数据科学环境

在“数据实验室”一节中,本章将数据收集、排队索引和分析操作结合到一个数据科学环境中。JupyterHub 被配置为提供 JupyterLab 实例(在第四章中介绍),以通过利用对 Kafka、Elasticsearch、Mosquitto 等的集群内访问来增强数据科学活动。

发展环境

前几章利用了来自低成本主机提供商 Vultr 和 Scaleway 的通用计算资源。本章通过选择 Hetzner 的折扣计算产品延续了这一趋势。Hetzner 是构建低成本开发和实验性 Kubernetes 集群的另一个绝佳选择。

本章中的 Kubernetes 集群利用了 Hetzner 的一个 CX21 (2 个 vCPU/8G RAM/40G SSD)和四个 CX41 (4 个 vCPU/16G RAM/160G SSD)实例。在写这篇文章的时候,Hetzner 的定价是每天不到 4 美元。

Note

Hetzner 上的 Ubuntu server 实例不包含某些包或内核模块rbd(Ceph 要求)所需的内核头。在每台 Hetzner 服务器上使用以下命令安装内核头文件并加载清单 6-1 中所示的rbd

$ # install kernel headers
$ apt install -y linux-headers-$(uname -r)

$ # load the Ceph rbd kernel module
$ modprobe rbd

Listing 6-1Installing kernel headers and the rdb kernel module

本章中的自定义 Kubernetes 集群标记为dev4,按照第三章中的安装说明以及第五章中的清单进行设置。如果遵循上一章的内容,将清单从cluster-apk8s-dev3复制并应用到cluster-apk8s-dev4,如清单 6-2 所示。

.
└── cluster-apk8s-dev3
└── cluster-apk8s-dev4
    ├── 000-cluster
    │   ├── 00-ingress-nginx
    │   ├── 10-cert-manager
    │   ├── 20-rook-ceph
    │   └── 30-monitoring
    └── 003-data
        ├── 000-namespace
        ├── 010-zookeeper
  ├── 020-kafka
        └── 050-mqtt

Listing 6-2Development environment prerequisites

TLS 证书

本章使用子域kib表示 Kibana,auth表示 Keycloak,lab表示 JupyterHub/JupyterLab。通过生成证书,确保 TLS Kubernetes Secret data-production-tls可供 ingress 使用。清单 6-3 列出了定义文件cluster-apk8s-dev4/003-data/000-namespace/05-certs.yml的示例证书管理器配置。

apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: data-cert
  namespace: data
spec:
  secretName: data-production-tls
  issuerRef:
    name: letsencrypt-production
    kind: ClusterIssuer
  commonName: data.dev4.apk8s.dev
  dnsNames:
    - data.dev4.apk8s.dev
    - auth.data.dev4.apk8s.dev
    - lab.data.dev4.apk8s.dev
    - kib.data.dev4.apk8s.dev

Listing 6-3cluster-apk8s-dev4 TLS Certificate

基本认证

创建一个包含基本身份验证凭证的 Kubernetes 秘密。稍后,Ingress Nginx 被配置为使用这个秘密来保护对 Kibana 的访问。

首先,使用htpasswd 4 实用程序在名为auth的文件中创建一个用户名/密码组合。

$ cd cluster-apk8s-dev4/003-data/000-namespace

$ htpasswd -c auth sysop

使用 kubectl 命令create secret从按照 Ingress 的预期构建的auth文件中创建一个名为sysop-basic-auth的秘密。

$ kubectl create secret generic sysop-basic-auth \
   --from-file auth -n data

麋鹿

麋鹿栈 5 由 Elasticsearch、Logstash 和 Kibana 组成。ELK 是一个流行的应用套件,用于索引、搜索、路由、转换和可视化数据。Elasticsearch B.V .维护这个开源栈,并提供托管服务以及许多其他流行的开源和商业软件。

Note

Elasticsearch b . v .Elastic License对于包含 Elastic search 的平台即服务(PaaS)产品来说可能过于严格。亚马逊在 Apache 2.0 软件许可下(尽管有些争议 7 )分叉了该项目并创建了用于弹性搜索 6开放发行版。尽职调查是选择适合特定用例的发行版的一个要求。

弹性搜索

Elasticsearch 是一个基于 Apache Lucene 8 的数据索引器和分析引擎,它是一个专门为横向可伸缩性和高可用性而设计的分布式系统。Elasticsearch 接受任何形式的基于 JSON 的数据结构,这使得它非常适合与现代 web APIs 进行互操作。Elasticsearch 可以自动检测许多数据类型,但也可以提供自定义模板来断言需要转换的不明确字段的数据类型,例如表示为字符串或日期格式的数字。Elasticsearch 具有广泛的数据聚合和统计分析功能,可以将数据与其索引一起存储。尽管旨在数据索引和聚合,Elasticsearch 是一个有能力的 NoSQL 数据库。

本章设置了一个单节点 Elasticsearch 实例。Elasticsearch 的生产部署由多个节点组成,每个节点都致力于一个特定的任务:数据节点存储、索引和查询数据;主节点更新集群状态;客户端节点采用负载均衡器的形式,执行索引和搜索。

Note

Elasticsearch 的官方维护者 Elasticsearch B.V .开发了一个解决方案,他们称之为“Kubernetes 上的弹性云, 9 ”,实现了安装和管理 Elasticsearch 集群的 Kubernetes 操作模式。考虑将此解决方案用于生产实施。

创建目录cluster-apk8s-dev4/003-data/030-elasticsearch。在新的030-elasticsearch目录中,从清单 6-4 中创建一个名为10-service.yml的文件。

apiVersion: v1
kind: Service
metadata:
  namespace: data
  name: elasticsearch
spec:
  type: ClusterIP
  selector:
    app: elasticsearch
  ports:
    - name: http-es
      port: 9200
      targetPort: http-es
      protocol: TCP

Listing 6-4Elasticsearch Service

应用弹性搜索服务配置:

$ kubectl apply -f 10-service.yml

接下来,在清单 6-5 中名为40-statefulset.yml的文件中为 Elasticsearch 创建一个 StatefulSet 配置。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
  namespace: data
  labels:
    app: elasticsearch
spec:
  serviceName: elasticsearch
  replicas: 1 # single-node cluster
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      initContainers:
        - name: init-sysctl
          image: busybox:1.27.2
          command: ["sysctl", "-w", "vm.max_map_count=262144"]
          securityContext:
            privileged: true
        - name: init-chown
          image: busybox:1.27.2
          command: ["/bin/sh"]
          args: ["-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
          securityContext:
            privileged: true
          volumeMounts:
            - name: es-data
              mountPath: /usr/share/elasticsearch/data
      containers:
        - name: elasticsearch
          image: docker.elastic.co/elasticsearch/elasticsearch:7.1.1
          imagePullPolicy: IfNotPresent
          env:
          - name: "discovery.type"
            value: "single-node"
          - name: "cluster.name"
            value: "apk8s"
          - name: "transport.host"
            value: "127.0.0.1"
          - name: "ES_JAVA_OPTS"
            value: "-Xms512m -Xmx512m"
          - name: "http.host"
            value: "0.0.0.0"
          - name: "http.port"
            value: "9200"
          - name: "http.cors.allow-origin"
            value: "http://localhost:1358"
          - name: "http.cors.enabled"
            value: "true"
          - name: "http.cors.allow-headers"
            value: "X-Requested-With,X-Auth-Token,Content-Type,Content-Length,Authorization"
          - name: "http.cors.allow-credentials"
            value: "true"

          ports:
            - containerPort: 9200
              name: http-es
            - containerPort: 9300
              name: tcp-es
          volumeMounts:
            - name: es-data
              mountPath: /usr/share/elasticsearch/data
  volumeClaimTemplates:
    - metadata:
        name: es-data
      spec:
        storageClassName: rook-ceph-block
        accessModes: [ ReadWriteOnce ]
        resources:
          requests:
            storage: 50Gi

Listing 6-5Elasticsearch StatefulSet

应用弹性搜索状态集配置:

$ kubectl apply -f 40-statefulset.yml

使用kubectl将 Elasticsearch 服务移植到本地工作站。

$ kubectl port-forward elasticsearch-0 9200:9200 -n data

使用curl检查新的单节点 Elasticsearch 集群的健康状况。成功的安装会返回一个 JSON 对象,其状态键报告消息green

$ curl http://localhost:9200/_cluster/health

Elasticsearch 被设计成分片 10 并跨大型节点集群复制数据。单节点开发集群只支持每个索引一个分片,并且不能复制数据,因为没有其他节点可用。使用curlPOST一个(JSON)模板到这个单节点集群,通知 Elasticsearch 用一个 shard 和零个副本配置任何新的索引。

$ cat <<EOF | curl -X POST \
-H "Content-Type: application/json" \
-d @- http://localhost:9200/_template/all
{
  "index_patterns": "*",
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  }
}
EOF

logstash(日志记录)

Logstash 是弹性生态系统中的中心枢纽。“Logstash 是一个开源的服务器端数据处理流水线,它可以同时从多个来源获取数据,对其进行转换,然后将其发送到您最喜欢的‘stash’。” 11 本书使用 Logstash 将数据注入到 Elasticsearch 中。将大量数据高速注入到弹性搜索中具有挑战性;然而,Logstash 缓冲数据并管理由索引过程引起的背压。Logstash 有一大套有用的输入插件,包括 Apache Kafka,本章稍后将利用这些插件将 Kafka 主题中的记录(事件/消息)索引到 Elasticsearch 中。

要运行本章中的示例,请确保 Apache Kafka 正在 Kubernetes 集群中运行。如有必要,查看第章至第章的 Kafka 安装说明。

创建目录cluster-apk8s-dev4/003-data/032-logstash。在新的032-logstash目录中,从清单 6-6 中创建一个名为10-service.yml的文件。

kind: Service
apiVersion: v1
metadata:
  name: logstash
  namespace: data
spec:
  selector:
    app: logstash
  ports:
  - protocol: TCP
    port: 5044
  type: ClusterIP

Listing 6-6Logstash Service

应用日志存储服务配置:

$ kubectl apply -f 10-service.yml

接下来,在清单 6-7 中的一个名为30-configmap-config.yml的文件中创建一个包含 Logstash 配置设置的 ConfigMap。要在资源受限的环境中限制内存使用(如本章中定义的开发集群),请将 Java JVM -Xms512m 和-Xmx523m 设置配置为相对较小的值。

apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-config
  namespace: data
data:
  logstash.yml: |
    http.host: "0.0.0.0"
    xpack.monitoring.enabled: false

  pipelines.yml: |
    - pipeline.id: main
      path.config: "/usr/share/logstash/pipeline"
  log4j2.properties: |
    status = error
    name = LogstashPropertiesConfig

    appender.console.type = Console
    appender.console.name = plain_console
    appender.console.layout.type = PatternLayout
    appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %m%n

    appender.json_console.type = Console
    appender.json_console.name = json_console
    appender.json_console.layout.type = JSONLayout
    appender.json_console.layout.compact = true
    appender.json_console.layout.eventEol = true

    rootLogger.level = ${sys:ls.log.level}
    rootLogger.appenderRef.console.ref = ${sys:ls.log.format}_console
  jvm.options: |
    ## JVM configuration

    -Xms512m
    -Xmx523m

    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=75
    -XX:+UseCMSInitiatingOccupancyOnly
    -Djava.awt.headless=true
    -Dfile.encoding=UTF-8
    -Djruby.compile.invokedynamic=true
    -Djruby.jit.threshold=0
    -XX:+HeapDumpOnOutOfMemoryError
    -Djava.security.egd=file:/dev/urandom

Listing 6-7Logstash configuration

ConfigMap

应用日志存储配置配置图:

$ kubectl apply -f 30-configmap-config.yml

Logstash 分三个阶段处理事件:输入、过滤和输出。清单 6-8 中的配置演示了使用 Kafka 输入插件来消费来自主题消息和指标的数据。输出配置检查 Kafka 主题是否存在,如果找到,则将数据从主题路由到以当前日期为前缀的相应索引中。

img/483120_1_En_6_Fig1_HTML.jpg

图 6-1

Kafka 到 Elasticsearch Logstash 流水线配置

在清单 6-8 中的一个名为30-configmap-pipeline.yml的文件中创建一个包含 Logstash 流水线设置的 ConfigMap。

apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-pipeline
  namespace: data
data:
  logstash.conf: |
    input {
      kafka {
        bootstrap_servers => "kafka-headless:9092"
        topics => [ "messages", "metrics"]
        auto_offset_reset => "latest"
        auto_commit_interval_ms => "500"
        enable_auto_commit => true
        codec => json
        decorate_events => true
      }
    }

    output {
      if [@metadata][kafka][topic] {
        elasticsearch {
            hosts => [ "elasticsearch:9200" ]
            index => "apk8s-%{[@metadata][kafka][topic]}-%{+YYYY.MM.dd}"
        }
      }
    }

Listing 6-8Logstash pipeline configuration ConfigMap

应用 Logstash 流水线配置配置图:

$ kubectl apply -f 30-configmap-pipeline.yml

最后,在清单 6-9 中的一个名为40-deployment.yml的文件中创建一个 Logstash 部署。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: logstash
  namespace: data
  labels:
    app: logstash
spec:
  replicas: 1
  selector:
    matchLabels:
      app: logstash
  template:
    metadata:
      labels:
        app: logstash
    spec:
      containers:
      - name: logstash
        image: docker.elastic.co/logstash/logstash:7.1.1
        ports:
        - containerPort: 5044
        env:
        - name: ES_VERSION
          value: 7.1.1
        volumeMounts:
        - name: config-volume
          mountPath: /usr/share/logstash/config
        - name: logstash-pipeline-volume
          mountPath: /usr/share/logstash/pipeline
      volumes:

      - name: config-volume
        configMap:
          name: logstash-config
      - name: logstash-pipeline-volume
        configMap:
          name: logstash-pipeline

Listing 6-9Logstash Deployment

应用 Logstash 部署:

$ kubectl apply -f 40-deployment.yml

集群现在运行单个 Logstash 实例,可以通过按需增加副本来轻松扩展。从单个节点开始对早期调试很有用。

如前所述,Logstash 流水线接受来自 Kafka 主题消息和指标的 JSON 数据输入。输出配置指示 Logstash 根据主题名称和日期填充 Elasticsearch 索引。

Note

参见第五章,了解设置 Apache Kafka 和本章示例所需的kafka-test-client Pod 的说明。

通过向由kafka-test-client提供的kafka-console-producer脚本回显一个简单的 JSON 消息来测试新的 Logstash 流水线。

$ kubectl -n data exec -it kafka-test-client -- \
bash -c "echo '{\"usr\": 1, \"msg\": \"Hello ES\" }' | \
kafka-console-producer --broker-list kafka:9092 \
--topic messages"

使用kubectl将 Elasticsearch 服务移植到本地工作站。

$ kubectl port-forward elasticsearch-0 9200:9200 -n data

接下来,确保 Logstash 将 Kafka 主题消息中的数据事件正确地路由到正确的索引中。使用curl获取索引模式apk8s-messages-*的所有记录。以下命令从以apk8s-messages-开头的索引中返回所有记录:

$ curl http://localhost:9200/apk8s-messages-*/_search
Example response:
{
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "apk8s-messages-2020.03.02",
                "_type": "_doc",
                "_id": "IDn7mXABZUrIUU7qdxAr",
                "_score": 1.0,
                "_source": {
                    "@version": "1",
                    "usr": 1,
                    "@timestamp": "2020-03-02T06:42:38.545Z",
                    "msg": "Hello ES"
                }
            }
        ]
    }
}

在本章的前面,模板all被定义为匹配所有索引并设置默认分片和复制。可以添加额外的模板来定义特定索引或索引集上的字段的数据类型。然而,当没有模板匹配字段或索引时,Elasticsearch 会尽最大努力猜测数据类型。使用以下 curl 命令查看为新索引生成的默认映射:

$ curl http://localhost:9200/apk8s-messages-*/_mapping

对于在该集群中配置的 Elasticsearch 7.1.1,字段user接收数字赋值 long ,字段msg索引为文本

Logstash 缓冲和管理来自弹性搜索索引操作的反压力的能力,在以非常高的速度消耗和处理大量数据的平台中可以发挥重要作用。即使在本章定义的小型集群中,Logstash 也可能将 12 扩展到十几个或更多的节点,每个节点都将数据消耗和缓冲到这个单一的 Elasticsearch 节点中。

巴拉人

Kibana 是 ELK 栈的前端组件,与 Elasticsearch 无缝集成,是调试、开发和可视化 Elasticsearch 数据的优秀工具。然而,Kibana 也仅限于与 Elasticsearch 合作。现代分析、可视化和仪表板通常需要收集、处理和可视化来自各种提供商的数据。利用 Kibana 作为 Elasticsearch 的内部开发和调试工具,同时使用更通用的解决方案进行跨平台的可视化和分析,这种情况并不少见。

创建目录cluster-apk8s-dev4/003-data/034-kibana。在新的034-kibana目录中,从清单 6-10 中创建一个名为10-service.yml的文件。

apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: data
  labels:
    app: kibana
spec:
  selector:
    app: kibana
  ports:
    - protocol: "TCP"
      port: 80
      targetPort: 5601
  type: ClusterIP

Listing 6-10Kibana Service

应用 Kibana 服务:

$ kubectl apply -f 10-service.yml

接下来,在清单 6-11 中的一个名为20-configmap.yml的文件中创建一个包含 Kibana 配置设置的 ConfigMap。

apiVersion: v1
kind: ConfigMap
metadata:
  name: kibana
  namespace: data
  labels:
    app: kibana
data:
  kibana.yml: |-
    server.name: kib.data.dev4.apk8s.dev
    server.host: "0"
    elasticsearch.hosts: http://elasticsearch:9200

Listing 6-11Kibana configuration ConfigMap

应用基巴纳配置图:

$ kubectl apply -f 20-configmap.yml

接下来,在清单 6-12 中的一个名为30-deployment.yml的文件中创建一个 Kibana 部署。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: data
  labels:
    app: kibana
spec:
  replicas: 1
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      volumes:
        - name: kibana-config-volume
          configMap:
            name: kibana
      containers:
        - name: kibana
          image: docker.elastic.co/kibana/kibana-oss:7.1.1
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: kibana-config-volume
              mountPath: /usr/share/kibana/config
          env:
            - name: CLUSTER_NAME
              value: apk8s
          ports:
            - name: http
              containerPort: 5601

Listing 6-12Kibana Deployment

应用 Kibana 部署:

$ kubectl apply -f 30-deployment.yml

接下来,在清单 6-13 中的一个名为50-ingress.yml的文件中为 Kibana 创建一个入口。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: kibana
  namespace: data
  labels:
    app: kibana
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: sysop-basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
spec:
  rules:
    - host: kib.data.dev4.apk8s.dev
      http:
        paths:
          - backend:
              serviceName: kibana
              servicePort: 5601
            path: /
  tls:
    - hosts:
        - kib.data.dev4.apk8s.dev
      secretName: data-production-tls

Listing 6-13Kibana Ingress

应用基巴纳入口:

$ kubectl apply -f 50-ingress.yml

下面的 ingress 在 https://kib.data.dev4.apk8s.dev 将 Kibana 公开,并使用基本 Auth 进行安全保护。秘密sysop-basic-auth包含基本认证的用户名和密码。

如果继续,新的 Elasticsearch 实例只包含一条记录。本章的其余部分将演示如何配置 JupyterLab 环境,该环境能够与本章中配置的 Elasticsearch 和前面配置的 Kafka 进行数据通信。

数据实验室

本章和上一章介绍了数据驱动平台的基本功能,包括 Kafka 的事件流、Mosquitto 的物联网消息代理、数据路由以及通过 Elasticsearch 在 Logstash 和持久索引数据中的转换。本章的剩余部分将创建一个用户提供的数据实验室,直接连接到集群中的这些系统,如图 6-2 所示。

JupyterLab 在第四章中首次介绍,它带来了一套健壮且可扩展的数据科学功能以及一个命令行终端。在集群中运行 JupyterLab 为传统的数据科学、分析和实验创建了一个非常高效的环境,并通过与 Kubernetes API 更紧密的交互提供了开发和运营的机会。

以下部分演示了允许 JupyterLab 访问 Kubernetes 资源的 Kubernetes 名称空间、示例 RBAC 和 ServiceAccount 权限的设置。JupyterHub 13 被配置为提供 JupyterLab 环境,针对 Keycloak 进行身份验证。

img/483120_1_En_6_Fig2_HTML.jpg

图 6-2

名称空间中的 JupyterLab

凯克洛克

Keycloak 14 是由红帽赞助的免费开源身份和访问管理应用。Keycloak 提供了创建和管理用户帐户的能力,或者连接到现有的 LDAP 或 Active Directory。第三方应用可以通过 OpenID Connect、OAuth 2.0 和 SAML 2.0 对用户进行身份验证。

Keycloak 为身份管理和第三方认证提供了一个交钥匙解决方案,非常适合本书中描述的数据平台的要求。以下部分实现了一个单节点 Keycloak 实例,本章稍后将使用该实例在为用户提供 JupyterLab 实例之前对用户进行身份验证。

创建目录cluster-apk8s-dev4/003-data/005-keycloak。在新的005-keycloak目录中,从清单 6-14 中创建一个名为10-service.yml的文件。

apiVersion: v1
kind: Service
metadata:
  name: web-keycloak
  namespace: data
spec:
  selector:
    app: web-keycloak
  ports:
    - name: http-web
      protocol: "TCP"
      port: 8080
      targetPort: http-web
  type: ClusterIP

Listing 6-14Keycloak Web Service

应用 Keycloak Web 服务:

$ kubectl apply -f 10-service.yml

接下来,在清单 6-15 中的一个名为15-secret.yml的文件中创建一个包含 Keycloak 管理员凭证的秘密。

apiVersion: v1
kind: Secret
metadata:
  name: keycloak
  namespace: data
  labels:
    app: keycloak
type: Opaque
stringData:
  keycloak_user: "sysop"
  keycloak_password: "verystrongpassword"
  keystore_password: "anotherverystrongpassword"

Listing 6-15Keycloak administrator and keystore credentials

应用密钥锁密码:

$ kubectl apply -f 10-service.yml

接下来,在清单 6-16 中的一个名为30-deployment.yml的文件中创建一个 Keycloak 部署。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web-keycloak
  namespace: data
spec:
  serviceName: web-keycloak
  replicas: 1
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: web-keycloak
  template:
    metadata:
      labels:
        app: web-keycloak
    spec:
      initContainers:
        - name: keycloak-init-chown
          image: alpine:3.10.1
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: keycloak-db
              mountPath: /data
          command: ["chown"]
          args: ["-R","1000:1000","/data"]
      containers:
      - name: web-keycloak
        image: jboss/keycloak:6.0.1
        imagePullPolicy: IfNotPresent
        env:

          - name: PROXY_ADDRESS_FORWARDING
            value: "true"
          - name: KEYCLOAK_HOSTNAME
            value: "auth.data.dev4.apk8s.dev"
          - name: KEYCLOAK_USER
            valueFrom:
              secretKeyRef:
                name: keycloak
                key: keycloak_user
          - name: KEYCLOAK_PASSWORD
            valueFrom:
              secretKeyRef:
                name: keycloak
                key: keycloak_password
          - name: KEYSTORE_PASSWORD
            valueFrom:
              secretKeyRef:
                name: keycloak
                key: keystore_password
        ports:
          - name: http-web
            containerPort: 8080
        volumeMounts:
          - name: keycloak-db
            mountPath: /opt/jboss/keycloak/standalone/data
  volumeClaimTemplates:
    - metadata:
        name: keycloak-db
      spec:
        storageClassName: rook-ceph-block
        accessModes: [ ReadWriteOnce ]
        resources:
          requests:
            storage: 5Gi

Listing 6-16Keycloak deployment

应用键盘锁部署:

$ kubectl apply -f 30-deployment.yml

最后,在清单 6-17 中的一个名为50-ingress.yml的文件中为 Keycloak 创建一个入口。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: web-auth
  namespace: data
  labels:
    app: web-auth
spec:
  rules:
  - host: auth.data.dev4.apk8s.dev
    http:
      paths:
      - backend:
          serviceName: web-keycloak
          servicePort: 8080
        path: /
  tls:
  - hosts:
    - auth.data.dev4.apk8s.dev
    secretName: data-production-tls

Listing 6-17Keycloak Ingress

应用钥匙锁入口:

$ kubectl apply -f 50-ingress.yml

领域、客户机和用户

Keycloak 通过领域的配置为多个租户提供身份管理和身份验证。 15 JupyterHub 在本章稍后配置为使用属于领域datalab的 Oauth2、 16 对用户进行身份验证。与一个领域相关联的 Keycloak 客户端向诸如 JupyterHub 之类的应用授予访问权限,以便对用户进行身份验证。本节将设置一个领域、客户机和用户,以便在本章后面提供 JupyterLab 服务器。

使用网络浏览器,按照上一节中的设置访问新入口 https://auth.data.dev4.apk8s.dev/auth/ 。使用清单 6-15 中定义的sysop凭证登录到 Keycloak。登录后,master 是用户界面左上角显示的默认领域,如图 6-3 所示。通过单击领域标题右侧的下拉菜单打开“添加领域”菜单,并创建新的领域 datalab。

img/483120_1_En_6_Fig3_HTML.jpg

图 6-3

添加领域

接下来,在新的 Datalab 领域的左侧导航中导航到客户端。点击“创建”并填写“添加客户端”表单,添加一个名为 datalab 的新客户端,如图 6-4 所示。

img/483120_1_En_6_Fig4_HTML.jpg

图 6-4

添加客户端

添加新的 datalab 客户端后,点击 Credentials 选项卡,检索生成的秘密,如图 6-5 所示。后来,JupyterHub 被配置为使用客户端 ID datalab 和生成的机密来获得根据 Keycloak datalab 领域对用户进行身份验证的权限。

img/483120_1_En_6_Fig5_HTML.jpg

图 6-5

客户端凭据

通过在上将授权启用切换到来配置新的 datalab 客户端(在设置选项卡下)。提供有效的重定向 URIs,在本例中, https://lab.data.dev4.apk8s.dev/hub/oauth_callback 稍后在" JupyterHub "一节中定义。复习图 6-6 。

img/483120_1_En_6_Fig6_HTML.jpg

图 6-6

客户端配置

最后,通过在左侧菜单的 Manage 部分下选择 users,在 datalab 领域中创建一个或多个用户。添加用户后,在“凭据”选项卡下分配密码。使用强密码;分配给该领域的任何用户以后都可以访问 JupyterLab 环境,有权在集群中读写数据和执行代码。

命名空间

大多数 Kubernetes 对象都与一个名称空间相关联,包括 pod、部署、状态集、作业、入口、服务等等,换句话说:“一个名称空间为多个种类的资源定义了一个逻辑命名的组。” 17 名称空间不仅有助于组织已配置的对象,它们还为安全性、资源分配和资源约束提供了选项。

Tip

使用 Kubernetes resource quota18对象对给定名称空间的资源进行细粒度限制,包括允许的 pod 和 PersistentVolumeClaims 的总数、CPU、内存和存储类限制。

本节设置名称空间data-lab以及 JupyterLab 和 JupyterHub 使用的 ServiceAccount 和 RBAC 权限。

创建目录cluster-apk8s-dev4/005-data-lab/000-namespace来包含名称空间范围的配置清单。接下来,在清单 6-18 中名为00-namespace.yml的文件中创建一个 Kubernetes 名称空间。

apiVersion: v1
kind: Namespace
metadata:
  name: data-lab

Listing 6-18data-lab Namespace

应用命名空间配置:

$ kubectl apply -f 00-namespace.yml

分配给该集群中的 pod 的默认服务帐户无权访问 Kubernetes API。下面创建一个分配给由 JupyterHub 提供的 JupyterLab Pods 的服务帐户。

从清单 6-19 中创建文件05-serviceaccount.yml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: data-lab
  namespace: data-lab

Listing 6-19data-lab ServiceAccount

应用 ServiceAccount 配置:

$ kubectl apply -f 05-serviceaccount.yml

接下来,为新的datalab ServiceAccount 创建一个角色,稍后分配给 JupyterLab,并为 hub ServiceAccount 创建一个角色,稍后由 JupyterHub 使用。从清单 6-20 中创建文件07-role.yml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: data-lab
  namespace: data-lab
rules:
  - apiGroups: [""]
    resources: ["pods","events","services"]
    verbs: ["get","watch","list","endpoints","events"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: hub
  namespace: data-lab
rules:
  - apiGroups: [""]
    resources: ["pods", "persistentvolumeclaims"]
    verbs: ["get","watch","list","create","delete"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["get", "watch", "list"]

Listing 6-20data-lab and hub Roles

for the data-lab Namespace

Note

Kubernetes 将“(apiGroups: [""])解释为核心 API 组。1920

应用角色配置:

$ kubectl apply -f 07-role.yml

最后,创建 RoleBinding 对象,将新角色与其对应的 ServiceAccounts 关联起来。从清单 6-21 中创建文件08-rolebinding.yml

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: data-lab
  namespace: data-lab
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: data-lab
subjects:
  - kind: ServiceAccount
    name: data-lab
    namespace: data-lab
---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: hub
  namespace: data-lab
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: hub
subjects:
  - kind: ServiceAccount
    name: hub
    namespace: data

Listing 6-21data-lab and hub RoleBindings

应用角色绑定配置:

$ kubectl apply -f 08-rolebinding.yml

JupyterHub

JupyterHub“产生、管理和代理单用户 Jupyter 笔记本服务器的多个实例。” 21 本节将 JupyterHub 安装到开发集群中,并将其配置为使用 Keycloak 对用户进行身份验证,并将 JupyterLab(笔记本)服务器生成到data-lab名称空间中。此外,上一节中定义的data-lab角色授予 JupyterHub 对 Kubernetes API 的有限访问权。

零到 JupyterHub22带 Kubernetes 是一个稳定的 JupyterHub 掌 Helm 图,文档全面详细。尽管为了学习和清晰起见,本书的大部分内容选择使用普通的 YAML 文件,但是 JupyterHub 是一个复杂的系统,这个图表很好地抽象了它,同时提供了任何必要的配置覆盖。

Note

Helm 是 Kubernetes 的一个成功且维护良好的软件包管理器,但也在快速发展中;因此,请参考官方文档 23 以获得简单的安装说明以及附加注释 24 从零到 JupyterHub

创建目录cluster-apk8s-dev4/003-data/100-jupterhub来包含 Helm 使用的values.yml清单。用清单 6-22 的内容填充。

注意values.ymlHelm 配置中的下列粗体元素:

  1. proxy部分,将secretToken设置为一个 32 个字符的随机十六进制值字符串。官方文档建议使用以下命令:

    $ openssl rand -hex 32.25
    
    
  2. singleuser部分,注意容器映像apk8s/datalab **。**这里可以使用各种 26 的 Jupyter 笔记本图片;然而,在这种情况下,指定的映像代表在第四章中开发的高度定制的版本。

  3. hub部分,extraConfig用于注入 Helm 表没有直接暴露的附加配置。在这种情况下,配置指示 KubeSpawner 27data-lab名称空间中生成 JupyterLab Pods,并配置为使用本章前面定义的data-lab ServiceAccount。

    此外,在hub部分中,extraEnv用于填充后面在 values.yml 中定义的GenericOAuthenticator所需的环境变量。注意 Keycloak 领域datalab,它在本章前面创建,并在环境变量 OAUTH2_AUTHORIZE_URLOAUTH2_TOKEN_URL 中定义。

  4. auth版块中,GenericOAuthenticator 28 配置了在键盘锁datalab领域中较早设置的client_idclient_secret。注意,数据实验室领域是token_urluserdata_url路径的一部分。

proxy:
  secretToken: "large_random_hex_string"
  service:
    type: ClusterIP

singleuser:
  image:
    name: apk8s/datalab
    tag: v0.0.5
  defaultUrl: "/lab"
  storage:
    dynamic:
      storageClass: rook-ceph-block
      capacity: 10Gi

hub:
  image:
    name: jupyterhub/k8s-hub
    tag: 0.9-dcde99a
  db:
    pvc:
      storageClassName: rook-ceph-block
  extraConfig:
    jupyterlab: |-
      c.Spawner.cmd = ['jupyter-labhub']
      c.KubeSpawner.namespace = "data-lab"
      c.KubeSpawner.service_account = "data-lab"
    jupyterhub: |-
      c.Authenticator.auto_login = True
  extraEnv:

    OAUTH2_AUTHORIZE_URL: https://auth.data.dev4.apk8s.dev/auth/realms/datalab/protocol/openid-connect/auth
    OAUTH2_TOKEN_URL: https://auth.data.dev4.apk8s.dev/auth/realms/datalab/protocol/openid-connect/token
    OAUTH_CALLBACK_URL: https://lab.data.dev4.apk8s.dev/hub/oauth_callback

scheduling:
  userScheduler:
    enabled: true
    replicas: 2
    logLevel: 4
    image:
      name: gcr.io/google_containers/kube-scheduler-amd64
      tag: v1.14.4

auth:
  type: custom
  custom:
    className: oauthenticator.generic.GenericOAuthenticator
    config:
      login_service: "Keycloak"
      client_id: "datalab"
      client_secret: "from_keycloak_client_config"
      token_url: https://auth.data.dev4.apk8s.dev/auth/realms/datalab/protocol/openid-connect/token
      userdata_url: https://auth.data.dev4.apk8s.dev/auth/realms/datalab/protocol/openid-connect/userinfo
      userdata_method: GET
      userdata_params: {'state': 'state'}
      username_key: preferred_username

Listing 6-22JupyterHub Helm values

将 JupyterHub 库 29 添加到 Helm 并更新。

$ helm repo add jupyterhub \
   https://jupyterhub.github.io/helm-chart/

$ helm repo update

安装(或升级/更新)JupyterHub 头盔包。

$ helm upgrade --install lab-hub jupyterhub/jupyterhub \
  --namespace="data" \
  --version="0.9-dcde99a" \
  --values="values.yml"

Note

JupyterHub 需要几分钟来拉取和初始化大型容器。如果在安装过程中超时,您可能需要重新运行 Helm 命令。

最后,在清单 6-23 中名为50-ingress.yml的文件中为 JupyterHub web 代理配置一个入口。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: jupyterhub
  namespace: data
spec:
  rules:
    - host: lab.data.dev4.apk8s.dev
      http:
        paths:
          - backend:
              serviceName: proxy-public
              servicePort: 80
            path: /
  tls:
    - hosts:
        - lab.data.dev4.apk8s.dev
      secretName: data-production-tls

Listing 6-23JupyterHub Ingress

应用入口配置:

$ kubectl apply -f 50-ingress.yml

JupyterHub 被配置为在集群和名称空间data中运行,并被配置为在data-lab名称空间中生成单用户 JupyterLab 服务器(pod)。应用配置后,JupyterHub 可能需要几分钟才能启动,因为它必须预加载大型 JupyterLab 映像。一旦 JupyterHub 完全启动,通过访问 https://lab.data.dev4.apk8s.dev ,启动一个新的 JupyterLab 实例(在datalab领域下的 Keycloak 中创建一个用户)。

JupyterLab

在第五章中首次介绍,JupyterLab 是“Jupyter 项目的下一代基于网络的用户界面”, 30 一个功能丰富的数据科学环境。Jupyter 项目始于 2014 年,已被大量采用;2018 年,GitHub 上公开分享的 31 Jupyter 笔记本超过 250 万台。Kubernetes 非常适合通过 JupyterHub 提供和服务 JupyterLab 环境,如前一节所述。

简化机器学习和统计模型的开发推动了 Jupyter 项目的成功。许多数据科学活动,如机器学习,需要静态的、不变的数据集来从实验中获得可重复的结果。然而,在 Jupyter 环境中运行静态数据以及实时事件流、索引和 Kubernetes 分布式计算的全部功能是一个直接在数据平台中心提供各种数据科学功能的机会。

以下部分演示了从集群内部直接使用数据和控制面板的简短示例,将 JupyterLab 笔记本电脑与 Kubernetes API、Kafka、Elasticsearch 和 Mosquitto MQTT 连接起来。

库伯勒斯 API

默认的 JupyterLab 环境包括一个 CLI(命令行界面)终端,在本章中使用的定制的 JupyterLab(在第四章中开发)提供了kubectl。图 6-7 展示了kubectl与 Kubernetes API 通信以检索当前名称空间中运行的 pod 列表。kubectl被允许通过自定义服务帐户和本章前面应用的 RBAC 配置访问 Kubernetes API。

img/483120_1_En_6_Fig7_HTML.jpg

图 6-7

在 JupyterLab 终端中运行 kubectl

图 6-8 描述了使用 Kubernetes 的官方 Python 客户端库,通过运行在基于 Python 的 Jupyter 笔记本中的代码与 Kubernetes API 进行通信。 32 将权限扩展到 JupyterLab Pod 使用的服务帐户可以允许 Python 执行任何 Kubernetes API 操作,例如,创建 Pod、Jobs、 33 CronJobs、 34 或与数据科学、分析或 ETL 活动相关的部署。

img/483120_1_En_6_Fig8_HTML.jpg

图 6-8

集群中运行 Python Kubernetes API 的 Jupyter 笔记本

从 Kubernetes 内部创建、管理和监控 Kubernetes 资源促进了机器学习自动化、深度学习和基于人工智能的解决方案部署方面的重大进展。Kubeflow(在第一章中描述)就是这样一个应用,它是一个“Kubernetes 的机器学习工具包”, 35 ,它充分利用 Kubernetes API 来自动化许多与机器学习相关的复杂任务。

Kafka

图 6-9 描绘了一个基于 Python 的 Jupyter 笔记本向 Kafka 主题metrics发布模拟设备传感器数据。从 Kafka 主题生成和消费数据只需要几行代码。Kafka 是服务之间交流事件和数据的强大渠道,包括 Jupyter 笔记本。在本章的前面,Logstash 被配置为消费来自选定 Kafka 主题的事件,并将它们交付给 Elasticsearch 进行索引和长期持久化。下一节演示如何从 Elasticsearch 的目的地检索图 6-9 中产生的数据。

img/483120_1_En_6_Fig9_HTML.jpg

图 6-9

Jupyter 笔记本运行 Python Kafka 制作人

弹性搜索

图 6-10 描述了一个针对任何以apk8s-metrics-开头的弹性搜索索引的简单match_all查询。在该示例中展示的查询可以被构建来执行跨越数十亿条记录的高级搜索、过滤、、?? 36和聚合 3738

img/483120_1_En_6_Fig10_HTML.jpg

图 6-10

用 Python 进行 Jupyter 笔记本弹性搜索

数据挖掘 Elasticsearch 的结果可以形成新的索引或导出为基于 CSV 的数据集,以便与机器学习结果一起打包和共享。

蚊子(MQTT)

MQTT 是物联网通信和指标收集的流行选择。第五章介绍了 Mosquitto 为数据流水线提供 MQTT 支持。图 6-12 只描绘了需要从 MQTT 主题中消费事件的几行代码。这个例子显示了从 MQTT.fx 应用发送到dev/apk8s/lightbulb主题的测试 JSON 消息,如图 6-11 所示,运行在本地工作站上。命令kubectl port-forward svc/mqtt 1883:1883 -n data允许 MQTT.fx 从本地端口连接到集群。

img/483120_1_En_6_Fig12_HTML.jpg

图 6-12

Jupyter 笔记本 Python MQTT 消费者

img/483120_1_En_6_Fig11_HTML.jpg

图 6-11

MQTT 测试实用程序

摘要

本章配置并演示了 ELK 栈(Elasticsearch、Logstash 和 Kibana ),以提供企业级的数据流水线、索引、分析和持久性。该集群现在支持 SSO、身份管理和通过 Keycloak 进行授权,key cloak 最初由 JupyterHub 用来对用户进行身份验证,并为 JupyterLab 实例提供对 Kubernetes API 的有限访问。如果您继续学习,Kubernetes 清单的结构应该类似于清单 6-24 。

这本书应该展示 Kubernetes 赋予平台架构师、软件开发人员、研究人员甚至爱好者的力量,让他们从各种一流的应用中快速构建现代数据平台。这本书是开发新颖解决方案的粗略草图和演示,这些解决方案将来自物联网、区块链和大数据技术的事件和数据通信到机器学习模型中,为基于推理的业务逻辑提供动力。

下一章继续通过数据湖和数据仓库的概念来支持数据。

.
└── cluster-apk8s-dev4
    ├── 000-cluster
    ├── 003-data
    │   ├── 000-namespace
    │   ├── 005-keycloak
    │   │   ├── 10-service.yml
    │   │   ├── 15-secret.yml
    │   │   ├── 30-deployment.yml
    │   │   └── 50-ingress.yml
    │   ├── 010-zookeeper
    │   ├── 020-kafka
    │   ├── 030-elasticsearch
    │   │   ├── 10-service.yml
    │   │   └── 40-statefulset.yml
    │   ├── 032-logstash
    │   │   ├── 10-service.yml
    │   │   ├── 20-configmap-config.yml
    │   │   ├── 20-configmap-pipeline.yml
    │   │   └── 30-deployment.yml
    │   ├── 034-kibana
    │   │   ├── 10-service.yml
    │   │   ├── 20-configmap.yml
    │   │   ├── 30-deployment.yml
    │   │   └── 50-ingress.yml
    │   └── 050-mqtt
    └── 005-data-lab
        └── 000-namespace
            ├── 00-namespace.yml
            ├── 05-serviceaccount.yml
            ├── 07-role.yml
            └── 08-rolebinding.yml

Listing 6-24Indexing and analytics development cluster configuration layout

Footnotes 1

海特,卡梅隆。"输入网络规模的信息."Gartner 博客网,2013 年 5 月 16 日。 https://blogs.gartner.com/cameron_haight/2013/05/16/enter-web-scale-it/

  2

洛尔,史蒂夫。"“大数据”的起源:一个词源侦探故事."比特博客(Blog),2013 年 2 月 1 日。 https://bits.blogs.nytimes.com/2013/02/01/the-origins-of-big-data-an-etymological-detective-story/

  3

迪克森詹姆斯。Pentaho、Hadoop 和数据湖詹姆斯·狄克逊的博客(Blog),2010 年 10 月 14 日。 https://jamesdixon.wordpress.com/2010/10/14/pentaho-hadoop-and-data-lakes/

  4

https://httpd.apache.org/docs/current/programs/htpasswd.html

  5

www.elastic.co/what-is/elk-stack

  6

https://opendistro.github.io/for-elasticsearch/

  7

伦纳德安德鲁。"开源开发者说,亚马逊已经从中立平台变成了残酷的竞争对手."中,2019 年 4 月 24 日。 https://onezero.medium.com/open-source-betrayed-industry-leaders-accuse-amazon-of-playing-a-rigged-game-with-aws-67177bc748b7

  8

https://lucene.apache.org/

  9

www.elastic.co/elasticsearch-kubernetes

  10

www.elastic.co/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster

  11

www.elastic.co/products/logstash

  12

www.elastic.co/guide/en/logstash/current/deploying-and-scaling.html

  13

https://jupyterhub.readthedocs.io/en/stable/

  14

www.keycloak.org/

  15

www.keycloak.org/docs/latest/server_admin/index.html#_create-realm

  16

https://oauth.net/2/

  17

https://github.com/kubernetes/community/blob/master/contributors/design-proposals/architecture/namespaces.md#design

  18

https://kubernetes.io/docs/concepts/policy/resource-quotas/

  19

https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-groups

  20

https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-examples

  21

朱庇特项目团队。" JupyterHub 文档。"2019 年 10 月 21 日。https://jupyterhub.readthedocs.io/en/stable/

  22

https://zero-to-jupyterhub.readthedocs.io/en/latest/

  23

https://helm.sh/docs/

  24

https://zero-to-jupyterhub.readthedocs.io/en/latest/setup-helm.html

  25

www. openssl. org/

  26

https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html

  27

https://jupyterhub-kubespawner.readthedocs.io/en/latest/spawner.html

  28

https://github.com/jupyterhub/oauthenticator

  29

https://zero-to-jupyterhub.readthedocs.io/en/latest/setup-jupyterhub/setup-helm.html

  30

朱庇特计划。" JupyterLab 文档。"2019. https://jupyterlab.readthedocs.io/

  31

为什么 Jupyter 是数据科学家的计算笔记本的选择。“自然 563(2018 年 10 月 30 日):145–46。 https://doi.org/10.1038/d41586-018-07196-1

  32

https://github.com/kubernetes-client/python

  33

https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/

  34

https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/

  35

www.kubeflow.org

  36

www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html

  37

www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html

  38

约多克·巴特洛格。“在 900 毫秒内查询 240 亿条记录。”弹性博客。2012. www.elastic.co/videos/querying-24-billion-records-in-900ms