Kubernetes 上的大数据——容器入门

1,198 阅读19分钟

世界正在迅速产生大量数据,这些数据来自各种来源——移动设备、社交媒体、电子商务交易、传感器等等。这种数据爆炸通常被称为“大数据”。虽然大数据为企业和组织提供了获取宝贵见解的巨大机会,但它也带来了如何存储、处理、分析和从庞大且多样化的数据中提取价值的巨大复杂性。

这就是 Kubernetes 的用武之地。Kubernetes 是一个开源的容器编排系统,帮助自动化容器化应用程序的部署、扩展和管理。Kubernetes 为构建大数据系统带来了重要优势。它提供了一种标准方法,在任何基础设施上部署容器化的大数据应用程序。这使得应用程序可以轻松地在本地服务器或云提供商之间迁移。它还简化了根据需求扩展或缩小大数据应用程序的过程。根据使用情况,可以自动启动或关闭额外的容器。

Kubernetes 通过自我修复和自动重启失败的容器等功能,帮助确保大数据应用程序的高可用性。它还提供了一种统一的方式来部署、监控和管理不同的大数据组件。与分别管理每个系统相比,这减少了操作复杂性。

本书旨在为您提供利用 Kubernetes 构建稳健且可扩展的大数据管道的实用技能。您将学习如何在 Kubernetes 上容器化并部署诸如 Spark、Kafka、Airflow 等流行的大数据工具。本书涵盖了构建批处理和实时数据管道的架构最佳实践和实用示例。

最终,您将获得在 Kubernetes 上运行大数据工作负载的端到端视图,并具备构建高效数据平台的能力,这些平台支持分析和人工智能应用程序。无论您是数据工程师、数据科学家、DevOps 工程师还是在组织中推动数字化转型的技术领导者,您将获得的知识都将极具价值。

Kubernetes 的基础是容器。容器是当今数据工程中最常用的技术之一。它们允许工程师将软件打包成标准化单元,用于开发、运输和部署。在本章结束时,您将了解容器的基础知识,并能够使用 Docker 构建和运行自己的容器。

在本章中,我们将介绍容器是什么、为什么有用以及如何在您的本地机器上使用 Docker 创建和运行容器。容器解决了开发人员在不同环境之间移动应用程序时面临的许多问题。它们确保了应用程序及其依赖项一起打包并与底层基础设施隔离,这使得应用程序可以快速可靠地从一个计算环境迁移到另一个计算环境。

我们将从在您的本地系统上安装 Docker 开始,这是一个用于构建和运行容器的平台。我们将运行简单的 Docker 镜像,并学习基本的 Docker 命令。然后,我们将构建第一个包含简单 Python 应用程序的 Docker 镜像。我们将学习如何定义 Dockerfile 以高效指定我们应用程序的环境和依赖关系。然后,我们将作为容器运行我们的镜像,并探索如何访问应用程序和检查其日志。

容器是现代软件部署的关键技术。它们轻便、便携且可扩展,使您能够更快地构建和发布应用程序。您在本章中将学到的概念和技能将为使用容器和部署数据应用程序提供坚实的基础。到本章结束时,您将准备好开始构建和部署自己的容器化数据处理作业、API 和数据工程工具。

在本章中,我们将涵盖以下主要主题:

  • 容器架构
  • 安装 Docker
  • 开始使用 Docker 镜像
  • 构建您自己的镜像

技术要求

对于本章,您应该已经安装了 Docker。此外,需要一台至少具备 4 GB RAM(推荐 8 GB)的计算机,因为 Docker 可能会大量消耗计算机的内存。

本章的代码可在 GitHub 上获取。请访问以下链接,并进入 Chapter01 文件夹: github.com/PacktPublis…

容器架构

容器是一种操作系统级的虚拟化方法,我们可以利用它在单一主机上运行多个隔离的进程。容器允许应用程序在一个独立的环境中运行,拥有自己的依赖、库和配置文件,而无需全虚拟机(VM)的开销,这使得它们更轻量且更高效。

如果将容器与传统的 VM 进行比较,两者在几个方面有所不同。VM 在硬件级别进行虚拟化,创建一个完整的虚拟操作系统。而容器则在操作系统级别进行虚拟化。因此,容器共享宿主系统的内核,而 VM 则各有自己的内核。这使得容器的启动时间更快,通常以毫秒计,相比之下 VM 需要几分钟(值得注意的是,在 Linux 环境中,Docker 可以直接利用 Linux 内核的功能。而在 Windows 系统中运行时,它在一个比全 VM 轻的轻量级 Linux VM 中运行)。

此外,容器在资源隔离方面表现更佳,因为它们仅隔离应用层,而 VM 则隔离整个操作系统。容器是不可变的基础设施,使它们更加便携和一致,因为更新会创建新的容器镜像(版本),而不是就地更新。

由于这些差异,与 VM 相比,容器允许更高的密度、更快的启动时间和更低的资源使用率。单个服务器可以运行数十个或数百个彼此隔离的容器化应用程序。

Docker 是最流行的容器平台之一,提供了构建、运行、部署和管理容器的工具。Docker 架构包括 Docker 客户端、Docker 守护进程、Docker 注册表和 Docker 镜像。

Docker 客户端是一个命令行界面(CLI)客户端,用于与 Docker 守护进程交互,以构建、运行和管理容器。这种交互通过 REST API 进行。

Docker 守护进程是在宿主机上运行的后台服务,管理构建、运行和分发容器。它是所有容器运行的基础。

Docker 注册表是用来托管、分发和下载 Docker 镜像的仓库。Docker Hub 是默认的公共注册表,拥有许多预构建的镜像,但云提供商通常也有自己的私有容器注册表。

最后,Docker 镜像是用来创建 Docker 容器的只读模板。镜像定义了容器环境、依赖、操作系统、环境变量以及容器运行所需的所有内容。

图 1.1 显示了在 VM 中运行的应用程序与在容器中运行的应用程序之间的区别。

image.png

图 1.2 展示了容器如何在不同的层级上运行。底部是共享的内核。在此之上,我们可以根据需要拥有多个操作系统。在 Debian 操作系统层之上,我们可以看到 Java 8 镜像和 NGINX 镜像。Java 8 层被三个容器共享,其中一个仅包含镜像信息,另外两个使用另一个镜像,Wildfly。该图表展示了容器架构在资源共享和轻量化方面的高效性,因为它是建立在彼此隔离的库、依赖和应用程序层之上的。

image.png

安装 Docker

要开始使用 Docker,您可以通过您的 Linux 发行版的包管理器安装它,或者为 Mac/Windows 机器安装 Docker Desktop。

Windows

要在 Windows 上使用 Docker Desktop,您必须开启 WSL 2 功能。请参考此链接以获取详细说明:docs.microsoft.com/en-us/windo…。 之后,您可以按照以下步骤安装 Docker Desktop:

  1. 访问 www.docker.com/products/do… 并下载安装程序。
  2. 下载完成后,双击安装程序并按照提示进行操作。
  3. 在配置页面确保选择了“使用 WSL 2 而非 Hyper-V”的选项。这是推荐的使用方式。(如果您的系统不支持 WSL 2,此选项将不可用。不过您仍然可以使用 Hyper-V 运行 Docker。)
  4. 安装完成后,关闭安装程序以完成安装并启动 Docker Desktop。
  5. 如果您有任何疑问,可参考官方文档:docs.docker.com/desktop/ins…

macOS

在 macOS 上安装 Docker Desktop 非常简单:

  1. 访问 www.docker.com/products/do… 并下载 macOS 版本的安装程序。
  2. 双击安装程序并按照提示进行安装。
  3. 安装完成后,Docker Desktop 将自动启动。
  4. Docker Desktop 在 macOS 上原生运行,使用 HyperKit VM,不需要额外配置。当 Docker Desktop 首次启动时,它会提示您授权它访问磁盘。授权 Docker Desktop 以允许它访问您的文件系统上的文件。

Linux

在基于 Linux 的系统上安装 Docker 非常直接。您可以使用您的 Linux 发行版的包管理器来执行这些操作,只需几个命令。例如,在 Ubuntu 上,首先要做的是移除之前在机器上安装的任何旧版本的 Docker:

$ sudo apt-get remove docker docker-engine docker.io containerd runc

您可以使用以下命令从默认的 apt 仓库安装 Docker:

$ sudo apt install docker.io

这将安装一个稍微旧一点的 Docker 版本。如果您想要最新版本,请检查官方 Docker 网站(docs.docker.com/desktop/ins…)并按照说明进行操作。

如果您希望在不需要使用 sudo 的情况下使用 Docker,运行以下命令:

$ sudo groupadd docker
$ sudo usermod –aG docker <YOUR_USERNAME>

现在,让我们开始 Docker 的实践操作。

开始使用 Docker 镜像

我们可以运行的第一个 Docker 镜像是 hello-world 镜像。它通常用于测试 Docker 是否正确安装和运行。

hello-world

安装完成后,打开终端(Windows 中的命令提示符)并运行以下命令:

$ docker run hello-world

此命令将从 Docker Hub 公共仓库拉取 hello-world 镜像并运行其中的应用程序。如果您成功运行,将看到以下输出:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
70f5ac315c5a: Pull complete
Digest: sha256:88ec0acaa3ec199d3b7eaf73588f4518c25 f9d34f58ce9a0df68429c5af48e8d
Status: Downloaded newer image for hello-world:latest
Hello from Docker!

这条消息表明您的安装看起来是正常的。 为了生成这条消息,Docker 执行了以下步骤:

  1. Docker 客户端联系了 Docker 守护进程。
  2. Docker 守护进程从 Docker Hub 拉取了 "hello-world" 镜像。
  3. Docker 守护进程从该镜像创建了一个新容器,运行生成您当前正在阅读的输出的可执行文件。
  4. Docker 守护进程将该输出流式传输到 Docker 客户端,客户端再发送到您的终端。

要尝试更有野心的事情,您可以运行一个 Ubuntu 容器:

$ docker run -it ubuntu bash

通过免费的 Docker ID 分享镜像、自动化工作流等: hub.docker.com/ 更多示例和想法,请访问: docs.docker.com/get-started…

恭喜您!您刚刚运行了您的第一个 Docker 镜像!现在,让我们尝试一些更具挑战性的事情。

NGINX

NGINX 是一个著名的开源软件,用于 Web 服务、反向代理和缓存。它在基于 Kubernetes 的架构中广泛使用。 与 hello-world 应用程序(表现像一次性任务执行)不同,NGINX 表现得像一个服务。它打开一个端口并持续监听用户请求。我们可以开始通过以下命令搜索 Docker Hub 中可用的 NGINX 镜像:

$ docker search nginx

输出将显示几个可用的镜像。通常,列表中的第一个是官方镜像。现在,要设置运行中的 NGINX 容器,我们可以使用以下命令:

$ docker pull nginx:latest

这将下载当前最新版本的镜像。冒号后的 latest 关键字代表这个镜像的“标签”。要安装特定版本(推荐),像这样指定标签:

$ docker pull nginx:1.25.2-alpine-slim

您可以访问 hub.docker.com/_/nginx 查看所有可用的标签。 现在,要运行容器,您应该指定您想要使用的镜像版本。以下命令将完成此操作:

$ docker run --name nginxcontainer –p 80:80 nginx:1.25.2-alpine-slim

您将开始在终端看到 NGINX 的日志。然后,打开您喜欢的浏览器并输入 http://localhost/。您应该看到此消息(图1.3):

image.png

Julia

在这最后一个例子中,我们将学习如何通过与运行中的容器交互使用未在我们机器上安装的技术。我们将运行一个使用名为 Julia 的新型高效数据科学编程语言的容器。为此,执行以下命令:

$ docker run -it --rm julia:1.9.3-bullseye

请注意,docker run 命令会查找本地镜像。如果没有下载,Docker 将自动从 Docker Hub 拉取镜像。通过前面的命令,我们将启动一个 Julia 1.9.3 容器的交互式会话。-it 参数允许我们交互式使用它。--rm 参数表明容器在停止后将自动被移除,因此我们不必手动移除它。

容器启动并运行后,让我们用一个简单的自定义函数来计算两个描述性统计量:均值和标准差。您将在终端看到 Julia 的标志,并可以使用以下命令:

$ using Statistics
$ function descriptive_statistics(x)
    m = mean(x)
    sd = std(x)
    return Dict("mean" => m, "std_dev" => sd)
  end

定义函数后,我们将用一组随机数字的小数组运行它:

$ myvector = rand(5)
$ descriptive_statistics(myvector)

您应该在屏幕上看到正确的输出。恭喜!您刚刚使用了 Julia 编程语言,而无需在计算机上安装或配置它,感谢 Docker!退出容器,请使用以下命令:

$ exit()

由于我们使用了 --rm 参数,如果我们运行 docker ps -a 命令,我们将看到它已被自动移除。

构建自己的镜像

现在,我们将自定义我们自己的容器镜像,用于运行一个简单的数据处理作业和一个 API 服务。

批处理作业

这里是一个简单的 Python 代码,用于批处理作业: run.py

import pandas as pd
url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv'
df = pd.read_csv(url, header=None)
df["newcolumn"] = df[5].apply(lambda x: x*2)
print(df.columns)
print(df.head())
print(df.shape)

这段 Python 代码从一个 URL 加载 CSV 数据集到 pandas DataFrame,通过将现有列乘以 2 添加一个新列,然后打印出 DataFrame 的一些信息(列名、前五行和 DataFrame 的大小)。使用您喜欢的代码编辑器键入此代码,并将文件保存为 run.py。

通常,我们首先在本地测试我们的代码(只要可能),以确保它能正常工作。为此,首先你需要安装 pandas 库:

pip3 install pandas

然后,用以下命令运行代码:

python3 run.py

如果一切顺利,你应该会看到这样的输出:

Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 'newcolumn'], dtype='object')
   0    1   2   3    4     5      6   7  8  newcolumn
0  6  148  72  35    0  33.6  0.627  50  1       67.2
1  1   85  66  29    0  26.6  0.351  31  0       53.2
2  8  183  64   0    0  23.3  0.672  32  1       46.6
3  1   89  66  23   94  28.1  0.167  21  0       56.2
4  0  137  40  35  168  43.1  2.288  33  1       86.2
(768, 10)

现在,我们准备将我们的简单处理作业打包进一个容器。让我们开始定义一个 Dockerfile: Dockerfile_job

FROM python:3.11.6-slim
RUN pip3 install pandas
COPY run.py /run.py
CMD python3 /run.py

这四行是我们定义一个工作容器所需要的全部内容。第一行指定要使用的基础镜像,这是 Python 3.11.6 的精简版本。这是一个基于 Debian 的操作系统,已经安装了 Python 3.11.6,这可以为我们节省大量时间。使用精简镜像非常重要,以保持容器大小小,优化传输时间和存储成本(当这是情况时)。

第二行安装了 pandas 库。第三行将本地的 run.py 文件复制到容器中。最后一行设置当容器启动时运行的默认命令来执行 /run.py 脚本。定义完代码后,将其保存为 Dockerfile_job(无扩展名)。现在是时候构建我们的 Docker 镜像了:

docker build -f Dockerfile_job -t data_processing_job:1.0 .

docker build 命令根据 Dockerfile 的指令构建镜像。通常,这个命令期望一个名为 Dockerfile 的文件。由于我们使用的文件名与预期的不同,我们必须告诉 Docker 使用 -f 标志指定哪个文件。-t 标志为镜像定义了一个标签。它由名称和版本组成,用冒号(:)分隔。在这种情况下,我们为镜像设置的名称是 data_processing_job 和 1.0 版本。这个命令的最后一个参数是代码文件所在的路径。这里,我们将当前文件夹设置为点(.)。这个点很容易忘记,所以要小心!

构建完成后,我们可以用这个命令检查本地可用的镜像:

docker images

你应该看到输出的第一行显示你最近构建的数据处理镜像:

REPOSITORY       TAG  IMAGE ID      CREATED        SIZE
data_process...  1.0  39bae1eb068c  6 minutes ago  351MB

现在,要从容器内部运行我们的数据处理作业,使用此命令:

docker run --name data_processing data_processing_job:1.0

docker run 命令运行指定的镜像。--name 标志定义容器的名称为 data_processing。在你开始运行容器后,你应该会看到之前相同的输出:

Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 'newcolumn'], dtype='object')
   0    1   2   3    4     5      6   7  8  newcolumn
0  6  148  72  35    0  33.6  0.627  50  1       67.2
1  1   85  66  29    0  26.6  0.351  31  0       53.2
2  8  183  64   0    0  23.3  0.672  32  1       46.6
3  1   89  66  23   94  28.1  0.167  21  0       56.2
4  0  137  40  35  168  43.1  2.288  33  1       86.2
(768, 10)

最后,不要忘记从您的环境中移除已退出的容器:

docker ps –a
docker rm data_processing

恭喜!您已经使用容器运行了您的第一个作业。现在,让我们转到另一种类型的容器化应用程序:服务。

API 服务

在这一部分,我们将使用 FastAPI 开发一个简单的 Python API。在您喜欢的代码编辑器中打开一个 Python 脚本,创建一个名为 app 的文件夹,并创建一个名为 main.py 的 Python 脚本。 在脚本中,首先我们导入 FastAPI 和 random 模块:

from fastapi import FastAPI
import random

接下来,我们创建一个 FastAPI 应用的实例:

app = FastAPI()

下一个代码块定义了一个路由,使用 @app.get 装饰器:

@app.get("/api")
async def root():
    return {"message": "You are doing great with FastAPI..."}

@app.get 装饰器表示这是一个 GET 端点。这个函数被定义为在 "/api" 路由上回答。它只是在请求该路由时返回一个愉快的消息。

下一个代码块定义了一个路由,"/api/{name}",其中 name 是将在请求中接收的参数。它返回一个带有给定名称的问候消息:

@app.get("/api/{name}")
async def return_name(name):
    return {
        "name": name,
        "message": f"Hello, {name}!"
    }

最后一个代码块定义了一个 "/joke" 路由。这个函数返回一个(非常有趣的!)随机笑话,从之前定义的笑话列表中选取。随意用您自己的酷笑话替换它们:

@app.get("/joke")
async def return_joke():
    jokes = [
        "What do you call a fish wearing a bowtie? Sofishticated.",
        "What did the ocean say to the beach? Nothing. It just waved",
        "Have you heard about the chocolate record player? It sounds pretty sweet."
    ]
    return {
        "joke": random.choice(jokes)
    }

重要的是要注意,每个函数都返回一个 JSON 格式的响应。这是 API 中非常常见的模式。对于整个 Python 代码,请参考书籍的 GitHub 仓库(github.com/PacktPublis…

在我们构建 Docker 镜像之前,建议在本地测试代码(只要可能)。为此,您必须安装 fastapi 和 uvicorn 包。在终端运行此命令:

pip3 install fastapi uvicorn

要运行 API,请使用以下命令:

uvicorn app.main:app --host 0.0.0.0 --port 8087

如果一切顺利,您将在终端看到这样的输出:

INFO:  Started server process [14417]
INFO:  Waiting for application startup.
INFO:  Application startup complete.
INFO:  Uvicorn running on http://0.0.0.0:8087 (Press CTRL+C to quit)

此命令在端口 8087 上本地运行 API 服务。要测试它,请打开浏览器并访问 http://localhost:8087/api。您应该在屏幕上看到编程消息。也测试]() http://localhost:8087/api/<YOUR_NAME> 和 http://localhost:8087/joke 端点。

现在我们知道一切都运行正常,让我们将 API 打包进一个 Docker 镜像。为此,我们将构建一个简单的 Dockerfile。为了优化它,我们将使用 alpine linux 发行版,这是一个极其轻量的基础操作系统。在项目的根文件夹中创建一个名为 Dockerfile 的新文件(无扩展名)。这是我们将用于此镜像的代码:

FROM python:3.11-alpine
RUN pip3 --no-cache-dir install fastapi uvicorn
EXPOSE 8087
COPY ./app /app
CMD uvicorn app.main:app --host 0.0.0.0 --port 8087

第一行导入一个基于 alpine Linux 发行版的 Python 容器。第二行安装 fastapi 和 uvicorn。第三行告知 Docker 在运行时容器将监听端口 8087。没有这个命令,我们将无法访问 API 服务。第四行将我们本地的 /app 文件夹中的所有代码复制到容器内的 /app 文件夹中。最后,CMD 命令指定容器启动时运行的命令。这里,我们启动 uvicorn 服务器运行我们的 FastAPI 应用。在 uvicorn 后,我们声明一个位置模式 folder.script_name

告诉 FastAPI 在哪里寻找 API 处理对象。

当这个 Dockerfile 构建成镜像时,我们将拥有一个配置了在端口 8087 上运行 FastAPI Web 服务器的容器化 Python 应用程序。Dockerfile 允许我们将应用程序及其依赖项打包成一个标准化的部署单元。要构建镜像,请运行以下命令:

docker build -t my_api:1.0 .

这里无需指定 -f 标志,因为我们使用的是默认名称的 Dockerfile。并记住命令行末尾的点!

现在,我们使用略有不同的参数运行容器:

docker run -p 8087:8087 -d --rm --name api my_api:1.0

-p 参数设置我们将在服务器(在这种情况下,是您的计算机)上的端口 8087 映射到容器中的端口 8087。如果我们不设置这个参数,将无法与容器进行任何通信。-d 参数在分离模式下运行容器。终端将不会显示容器日志,但当容器在后台运行时,它仍可用。--rm 参数设置容器完成后自动删除(非常方便)。最后,--name 为容器设置名称为 api。

我们可以用以下命令检查容器是否正确运行:

docker ps –a

如果您需要检查容器的日志,请使用以下命令:

docker logs api

您应该看到类似这样的输出:

INFO:  Started server process [1]
INFO:  Waiting for application startup.
INFO:  Application startup complete.
INFO:  Uvicorn running on http://0.0.0.0:8087 (Press CTRL+C to quit)

现在,我们可以在浏览器中用之前显示的链接测试我们的 API 端点(http://localhost:8087/api, http://localhost:8087/api/<YOUR_NAME> 和 http://localhost:8087/joke)。

恭喜!您正在从容器内部运行您的 API 服务。这是一个完全便携且自包含的应用程序,可以部署在任何地方。

要停止 API 服务,请使用以下命令:

docker stop api

要检查已停止的容器是否已自动移除,请使用以下命令:

docker ps -a

做得好!

总结

在本章中,我们介绍了容器的基础知识以及如何使用 Docker 构建和运行它们。容器提供了一种轻量级且便携的方式来打包应用程序及其依赖项,使它们可以在各种环境中可靠运行。

您了解了镜像、容器、Dockerfiles 和注册表等关键概念。我们安装了 Docker 并运行了像 NGINX 和 Julia 这样的简单容器,以获得实践经验。您为批处理作业和 API 服务构建了自己的容器,定义了 Dockerfiles 来打包依赖项。

这些技能使您能够开发应用程序并将它们容器化,以便在任何地方顺利部署。容器非常有用,因为它们确保您的软件每次都能按预期运行。

在下一章中,我们将研究使用 Kubernetes 编排容器,以便轻松扩展、监控和管理容器化应用程序。我们将了解 Kubernetes 的最重要概念和组件,并学习如何用 YAML 文件(清单)实现它们。