如何使用Helm在Kubernetes上使用MongoDB扩展Node.js应用程序

来自菜鸟教程
跳转至:导航、​搜索

介绍

Kubernetes 是一个用于大规模运行现代容器化应用程序的系统。 有了它,开发人员可以跨机器集群部署和管理应用程序。 尽管它可用于提高单实例应用程序设置的效率和可靠性,但 Kubernetes 旨在跨机器组运行应用程序的多个实例。

在使用 Kubernetes 创建多服务部署时,许多开发人员选择使用 Helm 包管理器。 Helm 通过提供协调这些对象如何交互的图表和模板来简化创建多个 Kubernetes 资源的过程。 它还为流行的开源项目提供了预打包的图表。

在本教程中,您将使用 Helm 图表将带有 MongoDB 数据库的 Node.js 应用程序部署到 Kubernetes 集群上。 您将使用 官方 Helm MongoDB 副本集图表 创建一个 StatefulSet 对象,由三个 Pods、一个 Headless Service 和三个PersistentVolumeClaims。 您还将创建一个图表以使用自定义应用程序映像部署多副本 Node.js 应用程序。 您将在本教程中构建的设置将反映 使用 Docker Compose 将 Node.js 应用程序容器化 中描述的代码的功能,并且将是使用 MongoDB 构建弹性 Node.js 应用程序的良好起点可以根据您的需求扩展的数据存储。

先决条件

要完成本教程,您需要:

  • 启用了基于角色的访问控制 (RBAC) 的 Kubernetes 1.10+ 集群。 此设置将使用 DigitalOcean Kubernetes 集群,但您可以自由地 使用另一种方法 创建集群。
  • kubectl 命令行工具安装在您的本地机器或开发服务器上并配置为连接到您的集群。 您可以在官方文档中阅读更多关于安装kubectl的信息。
  • Helm 安装在本地机器或开发服务器上,Tiller 安装在集群上,遵循 如何使用 Helm 包管理器 在 Kubernetes 集群上安装软件的步骤 1 和 2 中概述的说明。
  • Docker 安装在本地机器或开发服务器上。 如果您使用的是 Ubuntu 18.04,请按照 如何在 Ubuntu 18.04 上安装和使用 Docker 的步骤 1 和 2; 否则,请按照 官方文档 了解有关在其他操作系统上安装的信息。 确保将您的非 root 用户添加到 docker 组,如链接教程的第 2 步所述。
  • 一个 Docker Hub 帐户。 有关如何设置的概述,请参阅 this Introduction to Docker Hub。

第 1 步 — 克隆和打包应用程序

要将我们的应用程序与 Kubernetes 一起使用,我们需要将其打包,以便 kubelet 代理 可以拉取镜像。 但是,在打包应用程序之前,我们需要修改应用程序代码中的 MongoDB 连接 URI,以确保我们的应用程序可以连接到我们将使用 Helm mongodb-replicaset 图表。

我们的第一步是从 DigitalOcean 社区 GitHub 帐户 克隆 node-mongo-docker-dev 存储库。 此存储库包含 Containerizing a Node.js Application for Development With Docker Compose 中描述的设置中的代码,它使用带有 MongoDB 数据库的演示 Node.js 应用程序来演示如何设置开发环境码头工人撰写。 您可以在 From Containers to Kubernetes with Node.js 系列中找到有关应用程序本身的更多信息。

将存储库克隆到名为 node_project 的目录中:

git clone https://github.com/do-community/node-mongo-docker-dev.git node_project

导航到 node_project 目录:

cd node_project

node_project 目录包含用于处理用户输入的鲨鱼信息应用程序的文件和目录。 它已经现代化,可以与容器一起使用:敏感和特定的配置信息已从应用程序代码中删除并重构为在运行时注入,应用程序的状态已卸载到 MongoDB 数据库。

有关设计现代容器化应用程序的更多信息,请参阅 为 Kubernetes 构建应用程序为 Kubernetes 现代化应用程序

当我们部署 Helm mongodb-replicaset 图表时,它将创建:

  • 具有三个 Pod 的 StatefulSet 对象——MongoDB 副本集 的成员。 每个 Pod 都会有一个关联的 PersistentVolumeClaim,并且在重新调度时会保持一个固定的身份。
  • 由 StatefulSet 中的 Pod 组成的 MongoDB 副本集。 该集合将包括一个主要和两个次要。 数据将从主服务器复制到辅助服务器,确保我们的应用程序数据保持高可用性。

为了让我们的应用程序与数据库副本进行交互,我们代码中的 MongoDB 连接 URI 将需要包括副本集成员的主机名以及副本集本身的名称。 因此,我们需要在 URI 中包含这些值。

我们克隆的存储库中指定数据库连接信息的文件称为 db.js。 现在使用 nano 或您喜欢的编辑器打开该文件:

nano db.js

目前,该文件包括运行时在数据库连接 URI 中引用的 constants。 这些常量的值是使用 Node 的 process.env 属性注入的,该属性返回一个对象,其中包含有关运行时用户环境的信息。 在我们的应用程序代码中动态设置值允许我们将代码与底层基础设施分离,这在动态、无状态的环境中是必需的。 有关以这种方式重构应用程序代码的更多信息,请参阅 Containerizing a Node.js Application for Development With Docker ComposeStep 2 以及 The 12-Factor 中的相关讨论应用程序

连接 URI 和 URI 字符串本身的常量当前如下所示:

~/node_project/db.js

...
const {
  MONGO_USERNAME,
  MONGO_PASSWORD,
  MONGO_HOSTNAME,
  MONGO_PORT,
  MONGO_DB
} = process.env;

...

const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
...

为了与 12FA 方法保持一致,我们不想将副本实例的主机名或副本集名称硬编码到此 URI 字符串中。 现有的 MONGO_HOSTNAME 常量可以扩展为包含多个主机名——我们的副本集的成员——所以我们将保留它。 但是,我们需要在 URI 字符串的 选项部分 中添加一个副本集常量。

MONGO_REPLICASET 添加到 URI 常量对象和连接字符串:

~/node_project/db.js

...
const {
  MONGO_USERNAME,
  MONGO_PASSWORD,
  MONGO_HOSTNAME,
  MONGO_PORT,
  MONGO_DB,
  MONGO_REPLICASET
} = process.env;

...
const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?replicaSet=${MONGO_REPLICASET}&authSource=admin`;
...

在 URI 的选项部分中使用 replicaSet 选项 允许我们传入副本集的名称,它与 MONGO_HOSTNAME 常量中定义的主机名一起,将允许我们连接到集合成员。

完成编辑后保存并关闭文件。

修改数据库连接信息以使用副本集后,您现在可以打包应用程序,使用 docker build 命令构建映像,并将其推送到 Docker Hub。

使用 docker build-t 标志构建图像,这使您可以使用易于记忆的名称标记图像。 在这种情况下,使用您的 Docker Hub 用户名标记图像并将其命名为 node-replicas 或您自己选择的名称:

docker build -t your_dockerhub_username/node-replicas .

命令中的 . 指定构建上下文是当前目录。

构建映像需要一两分钟。 完成后,检查您的图像:

docker images

您将看到以下输出:

OutputREPOSITORY                              TAG                 IMAGE ID            CREATED             SIZE
your_dockerhub_username/node-replicas   latest              56a69b4bc882        7 seconds ago       90.1MB
node                                    10-alpine           aa57b0242b33        6 days ago          71MB

接下来,登录到您在先决条件中创建的 Docker Hub 帐户:

docker login -u your_dockerhub_username 

出现提示时,输入您的 Docker Hub 帐户密码。 以这种方式登录将使用您的 Docker Hub 凭据在非 root 用户的主目录中创建一个 ~/.docker/config.json 文件。

使用 docker push 命令 将应用程序镜像推送到 Docker Hub。 请记住将 your_dockerhub_username 替换为您自己的 Docker Hub 用户名:

docker push your_dockerhub_username/node-replicas

您现在拥有一个应用程序映像,您可以拉取该映像以使用 Kubernetes 运行您复制的应用程序。 下一步将配置特定参数以与 MongoDB Helm 图表一起使用。

第 2 步 — 为 MongoDB 副本集创建 Secret

stable/mongodb-replicaset 图表在使用 Secrets 时提供了不同的选项,我们将创建两个用于图表部署:

  • 我们的 副本集密钥文件 的秘密,它将作为副本集成员之间的共享密码,允许他们对其他成员进行身份验证。
  • 我们的 MongoDB 管理员用户的秘密,将在 admin 数据库上创建为 root 用户。 此角色将允许您在将应用程序部署到生产环境时创建具有有限权限的后续用户。

有了这些 Secret,我们将能够在专用值文件中设置我们的首选参数值,并使用 Helm 图表创建 StatefulSet 对象和 MongoDB 副本集。

首先,让我们创建密钥文件。 我们将使用带有 rand 选项的 openssl 命令 为密钥文件生成一个 756 字节的随机字符串:

openssl rand -base64 756 > key.txt

该命令生成的输出将被 base64 编码,确保统一的数据传输,并重定向到一个名为 key.txt 的文件,遵循 mongodb-replicaset 图表身份验证文档中所述的准则键本身 的长度必须介于 6 到 1024 个字符之间,仅包含 base64 集中的字符。

您现在可以使用此文件和 kubectl create 创建一个名为 keyfilesecret 的 Secret:

kubectl create secret generic keyfilesecret --from-file=key.txt

这将在 default 命名空间 中创建一个 Secret 对象,因为我们还没有为我们的设置创建一个特定的命名空间。

您将看到以下输出,表明您的 Secret 已创建:

Outputsecret/keyfilesecret created

删除 key.txt

rm key.txt

或者,如果您想保存文件,请确保 限制其权限 并将其添加到您的 .gitignore 文件 以使其不受版本控制。

接下来,为您的 MongoDB 管理员用户创建 Secret。 第一步是将您想要的用户名和密码转换为 base64。

转换您的数据库用户名:

echo -n 'your_database_username' | base64

记下您在输出中看到的值。

接下来,转换您的密码:

echo -n 'your_database_password' | base64

还要注意此处输出中的值。

打开 Secret 文件:

nano secret.yaml

注意:Kubernetes对象是通常使用YAML定义,严格禁止制表符,缩进需要两个空格。 如果您想检查任何 YAML 文件的格式,可以使用 linter 或使用 kubectl create--dry-run 和 [ X174X] 标志:

kubectl create -f your_yaml_file.yaml --dry-run --validate=true

通常,在使用 kubectl 创建资源之前验证您的语法是个好主意。


将以下代码添加到文件中以创建一个 Secret,该 Secret 将使用您刚刚创建的编码值定义 userpassword。 请务必将此处的虚拟值替换为您自己的 编码 用户名和密码:

~/node_project/secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mongo-secret
data:
  user: your_encoded_username
  password: your_encoded_password

在这里,我们使用 mongodb-replicaset 图表预期的键名:userpassword。 我们已将 Secret 对象命名为 mongo-secret,但您可以随意命名它。

完成编辑后保存并关闭文件。

使用以下命令创建 Secret 对象:

kubectl create -f secret.yaml

您将看到以下输出:

Outputsecret/mongo-secret created

同样,您可以删除 secret.yaml 或限制其权限并将其添加到您的 .gitignore 文件中。

创建 Secret 对象后,您可以继续指定将与 mongodb-replicaset 图表一起使用的参数值并创建 MongoDB 部署。

第 3 步 — 配置 MongoDB Helm 图表并创建部署

Helm 带有一个名为 stable 的积极维护的存储库,其中包含我们将使用的图表:mongodb-replicaset。 要将此图表与我们刚刚创建的秘密一起使用,我们将创建一个名为 mongodb-values.yaml 的配置参数值文件,然后使用此文件安装图表。

我们的 mongodb-values.yaml 文件将在很大程度上反映 mongodb-replicaset 图表存储库中默认的 values.yaml 文件。 但是,我们将对文件进行以下更改:

  • 我们将 auth 参数设置为 true 以确保我们的数据库实例以 启用授权 开始。 这意味着所有客户端都需要进行身份验证才能访问数据库资源和操作。
  • 我们将添加有关我们在上一步中创建的 Secret 的信息,以便图表可以使用这些值来创建副本集密钥文件和管理员用户。
  • 我们将减小与 StatefulSet 中每个 Pod 关联的 PersistentVolume 的大小,以使用 最小可行的 DigitalOcean 块存储单元 ,1GB,尽管您可以随意修改它以满足您的存储要求。

但是,在编写 mongodb-values.yaml 文件之前,您应该首先检查是否已创建并配置了一个 StorageClass 以供应存储资源。 数据库 StatefulSet 中的每个 Pod 都将具有粘性标识和关联的 PersistentVolumeClaim,这将为 Pod 动态配置 PersistentVolume。 如果重新调度 Pod,则 PersistentVolume 将挂载到调度 Pod 的任何节点(但如果永久删除关联的 Pod 或 StatefulSet,则必须手动删除每个卷)。

因为我们正在使用 DigitalOcean Kubernetes,我们的默认 StorageClass provisioner 设置为 dobs.csi.digitalocean.comDigitalOcean Block Storage — 我们可以通过键入以下内容进行检查:

kubectl get storageclass

如果您使用的是 DigitalOcean 集群,您将看到以下输出:

OutputNAME                         PROVISIONER                 AGE
do-block-storage (default)   dobs.csi.digitalocean.com   21m

如果您不使用 DigitalOcean 集群,则需要创建一个 StorageClass 并配置您选择的 provisioner。 具体操作方法请参见【X49X】官方文档【X75X】。

现在您已确保配置了 StorageClass,打开 mongodb-values.yaml 进行编辑:

nano mongodb-values.yaml

您将在此文件中设置将执行以下操作的值:

  • 启用授权。
  • 引用您的 keyfilesecretmongo-secret 对象。
  • 为您的 PersistentVolume 指定 1Gi
  • 将您的副本集名称设置为 db
  • 为集合指定 3 副本。
  • mongo 映像固定到撰写本文时的最新版本:4.1.9

将以下代码粘贴到文件中:

~/node_project/mongodb-values.yaml

replicas: 3
port: 27017
replicaSetName: db
podDisruptionBudget: {}
auth:
  enabled: true
  existingKeySecret: keyfilesecret
  existingAdminSecret: mongo-secret
imagePullSecrets: []
installImage:
  repository: unguiculus/mongodb-install
  tag: 0.7
  pullPolicy: Always
copyConfigImage:
  repository: busybox
  tag: 1.29.3
  pullPolicy: Always
image:
  repository: mongo
  tag: 4.1.9
  pullPolicy: Always
extraVars: {}
metrics:
  enabled: false
  image:
    repository: ssalaues/mongodb-exporter
    tag: 0.6.1
    pullPolicy: IfNotPresent
  port: 9216
  path: /metrics
  socketTimeout: 3s
  syncTimeout: 1m
  prometheusServiceDiscovery: true
  resources: {}
podAnnotations: {}
securityContext:
  enabled: true
  runAsUser: 999
  fsGroup: 999
  runAsNonRoot: true
init:
  resources: {}
  timeout: 900
resources: {}
nodeSelector: {}
affinity: {}
tolerations: []
extraLabels: {}
persistentVolume:
  enabled: true
  #storageClass: "-"
  accessModes:
    - ReadWriteOnce
  size: 1Gi
  annotations: {}
serviceAnnotations: {}
terminationGracePeriodSeconds: 30
tls:
  enabled: false
configmap: {}
readinessProbe:
  initialDelaySeconds: 5
  timeoutSeconds: 1
  failureThreshold: 3
  periodSeconds: 10
  successThreshold: 1
livenessProbe:
  initialDelaySeconds: 30
  timeoutSeconds: 5
  failureThreshold: 3
  periodSeconds: 10
  successThreshold: 1

persistentVolume.storageClass 参数在此处被注释掉:删除注释并将其值设置为 "-" 将禁用动态配置。 在我们的例子中,因为我们没有定义这个值,所以图表将选择默认的 provisioner — 在我们的例子中,dobs.csi.digitalocean.com

另请注意与 persistentVolume 键关联的 accessModeReadWriteOnce 表示配置的卷将只能由单个节点读写。 有关不同访问模式的更多信息,请参阅 文档

要了解有关文件中包含的其他参数的更多信息,请参阅 repo 中包含的 配置表

完成编辑后保存并关闭文件。

在部署 mongodb-replicaset 图表之前,您需要使用 helm repo update 命令 更新 stable repo:

helm repo update

这将从 stable 存储库中获取最新的图表信息。

最后,使用以下命令安装图表:

helm install --name mongo -f mongodb-values.yaml stable/mongodb-replicaset

注意: 在安装图表之前,您可以使用 --dry-run--debug 选项运行 helm install 来检查为您的版本生成的清单:

helm install --name your_release_name -f your_values_file.yaml --dry-run --debug your_chart

请注意,我们将 Helm 命名为 release mongo。 此名称将引用我们指定的配置选项的图表的此特定部署。 我们通过包含 -f 标志和我们的 mongodb-values.yaml 文件来指出这些选项。

另请注意,由于我们没有在 helm install 中包含 --namespace 标志,因此我们的图表对象将在 default 命名空间中创建。

创建发布后,您将看到有关其状态的输出,以及有关已创建对象的信息以及与它们交互的说明:

OutputNAME:   mongo
LAST DEPLOYED: Tue Apr 16 21:51:05 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME                              DATA  AGE
mongo-mongodb-replicaset-init     1     1s
mongo-mongodb-replicaset-mongodb  1     1s
mongo-mongodb-replicaset-tests    1     0s
...

您现在可以使用以下命令检查 Pod 的创建:

kubectl get pods

在创建 Pod 时,您将看到如下输出:

OutputNAME                         READY   STATUS     RESTARTS   AGE
mongo-mongodb-replicaset-0   1/1     Running    0          67s
mongo-mongodb-replicaset-1   0/1     Init:0/3   0          8s

此处的 READYSTATUS 输出表明 StatefulSet 中的 Pod 尚未完全准备好:与 Pod 的容器关联的 Init Containers 仍在运行。 因为 StatefulSet 成员是 按顺序 创建的,所以 StatefulSet 中的每个 Pod 必须是 RunningReady 才能创建下一个 Pod。

创建 Pod 并且所有关联的容器都在运行后,您将看到以下输出:

OutputNAME                         READY   STATUS    RESTARTS   AGE
mongo-mongodb-replicaset-0   1/1     Running   0          2m33s
mongo-mongodb-replicaset-1   1/1     Running   0          94s
mongo-mongodb-replicaset-2   1/1     Running   0          36s

Running STATUS 表示您的 Pod 已绑定到节点,并且与这些 Pod 关联的容器正在运行。 READY 表示一个 Pod 中有多少个容器正在运行。 有关详细信息,请参阅有关 Pod 生命周期的 文档

注意: 如果您在 STATUS 列中看到意外的阶段,请记住您可以使用以下命令对 Pod 进行故障排除:

kubectl describe pods your_pod
kubectl logs your_pod

StatefulSet 中的每个 Pod 都有一个名称,它结合了 StatefulSet 的名称和 Pod 的 序数索引 。 因为我们创建了三个副本,所以我们的 StatefulSet 成员编号为 0-2,每个都有一个 稳定的 DNS 条目 ,由以下元素组成:$(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local

在我们的例子中,由 mongodb-replicaset 图表创建的 StatefulSet 和 Headless Service 具有相同的名称:

kubectl get statefulset
OutputNAME                       READY   AGE
mongo-mongodb-replicaset   3/3     4m2s
kubectl get svc
OutputNAME                              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
kubernetes                        ClusterIP   10.245.0.1   <none>        443/TCP     42m
mongo-mongodb-replicaset          ClusterIP   None         <none>        27017/TCP   4m35s
mongo-mongodb-replicaset-client   ClusterIP   None         <none>        27017/TCP   4m35s

这意味着我们的 StatefulSet 的第一个成员将具有以下 DNS 条目:

mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local

因为我们需要我们的应用程序连接到每个 MongoDB 实例,所以我们必须拥有这些信息,以便我们可以直接与 Pod 通信,而不是与服务通信。 当我们创建自定义应用程序 Helm 图表时,我们将使用环境变量将每个 Pod 的 DNS 条目传递给我们的应用程序。

启动并运行数据库实例后,您就可以为 Node 应用程序创建图表了。

第 4 步 — 创建自定义应用程序图表并配置参数

我们将为我们的 Node 应用程序创建一个自定义 Helm 图表,并修改标准图表目录中的默认文件,以便我们的应用程序可以使用我们刚刚创建的副本集。 我们还将创建文件来为我们的应用程序定义 ConfigMap 和 Secret 对象。

首先,使用以下命令创建一个名为 nodeapp 的新图表目录:

helm create nodeapp

这将在您的 ~/node_project 文件夹中创建一个名为 nodeapp 的目录,其中包含以下资源:

  • 包含有关图表的基本信息的 Chart.yaml 文件。
  • 一个 values.yaml 文件,允许您设置特定的参数值,就像您在 MongoDB 部署中所做的那样。
  • 一个 .helmignore 文件,其中包含在打包图表时将被忽略的文件和目录模式。
  • templates/ 目录,其中包含将生成 Kubernetes 清单的模板文件。
  • 用于测试文件的 templates/tests/ 目录。
  • 此图表所依赖的任何图表的 charts/ 目录。

我们将在这些默认文件中修改的第一个文件是 values.yaml。 现在打开该文件:

nano nodeapp/values.yaml

我们将在此处设置的值包括:

  • 副本数。
  • 我们要使用的应用程序图像。 在我们的例子中,这将是我们在 Step 1 中创建的 node-replicas 图像。
  • 服务类型。 在这种情况下,我们将指定 LoadBalancer 来创建对我们的应用程序的访问点以用于测试目的。 因为我们正在使用 DigitalOcean Kubernetes 集群,所以当我们部署图表时,这将创建一个 DigitalOcean 负载均衡器 。 在生产环境中,您可以将图表配置为使用 Ingress ResourcesIngress Controllers 将流量路由到您的服务。
  • targetPort 用于指定 Pod 上将公开我们的应用程序的端口。

我们不会在这个文件中输入环境变量。 相反,我们将为 ConfigMap 和 Secret 对象创建模板,并将这些值添加到位于 ~/node_project/nodeapp/templates/deployment.yaml 的应用程序部署清单中。

values.yaml 文件中配置以下值:

~/node_project/nodeapp/values.yaml

# Default values for nodeapp.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 3

image:
  repository: your_dockerhub_username/node-replicas
  tag: latest
  pullPolicy: IfNotPresent

nameOverride: ""
fullnameOverride: ""

service:
  type: LoadBalancer
  port: 80
  targetPort: 8080
...

完成编辑后保存并关闭文件。

接下来,在nodeapp/templates目录下打开一个secret.yaml文件:

nano nodeapp/templates/secret.yaml

在此文件中,为您的 MONGO_USERNAMEMONGO_PASSWORD 应用程序常量添加值。 这些是您的应用程序期望在运行时访问的常量,如您的数据库连接文件 db.js 中所指定的。 当您添加这些常量的值时,请记住使用您之前在创建 mongo-secret 对象时在 Step 2 中使用的 base64-encoded 值。 如果您需要重新创建这些值,您可以返回到第 2 步并再次运行相关命令。

将以下代码添加到文件中:

~/node_project/nodeapp/templates/secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: {{ .Release.Name }}-auth
data:
  MONGO_USERNAME: your_encoded_username
  MONGO_PASSWORD: your_encoded_password

此 Secret 对象的名称将取决于您的 Helm 版本的名称,您将在部署应用程序图表时指定该名称。

完成后保存并关闭文件。

接下来,打开一个文件为您的应用程序创建一个 ConfigMap:

nano nodeapp/templates/configmap.yaml

在这个文件中,我们将定义我们的应用程序期望的其余变量:MONGO_HOSTNAMEMONGO_PORTMONGO_DBMONGO_REPLICASET。 我们的 MONGO_HOSTNAME 变量将包括副本集中 each 实例的 DNS 条目,因为这是 MongoDB 连接 URI 需要的

根据 Kubernetes 文档,当应用程序实现活跃度和就绪性检查时,连接 Pod 时应使用 SRV 记录。 如 Step 3 中所述,我们的 Pod SRV 记录遵循以下模式:$(statefulset-name)-$(ordinal).$(service name).$(namespace).svc.cluster.local。 由于我们的 MongoDB StatefulSet 实现了 liveness 和 readiness 检查,我们应该在定义 MONGO_HOSTNAME 变量的值时使用这些稳定的标识符。

将以下代码添加到文件中以定义 MONGO_HOSTNAMEMONGO_PORTMONGO_DBMONGO_REPLICASET 变量。 您可以为您的 MONGO_DB 数据库随意使用其他名称,但您的 MONGO_HOSTNAMEMONGO_REPLICASET 值必须按照此处显示的方式写入:

~/node_project/nodeapp/templates/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-config
data:
  MONGO_HOSTNAME: "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local,mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local"  
  MONGO_PORT: "27017"
  MONGO_DB: "sharkinfo"
  MONGO_REPLICASET: "db"

因为我们已经创建了 StatefulSet 对象和副本集,所以此处列出的主机名必须在您的文件中列出,与本示例中的显示完全相同。 如果您销毁这些对象并重命名您的 MongoDB Helm 版本,那么您将需要修改此 ConfigMap 中包含的值。 这同样适用于 MONGO_REPLICASET,因为我们在 MongoDB 版本中指定了副本集名称。

另请注意,此处列出的值是引用的,这是 Helm 中环境变量的期望值。

完成编辑后保存并关闭文件。

定义图表参数值并创建 Secret 和 ConfigMap 清单后,您可以编辑应用程序部署模板以使用您的环境变量。

第 5 步 — 将环境变量集成到您的 Helm 部署中

有了我们的应用程序 Secret 和 ConfigMap 的文件,我们需要确保我们的应用程序部署可以使用这些值。 我们还将自定义已在部署清单中定义的 活跃度和就绪性探测器

打开应用程序部署模板进行编辑:

nano nodeapp/templates/deployment.yaml

尽管这是一个 YAML 文件,但 Helm 模板使用与标准 Kubernetes YAML 文件不同的语法来生成清单。 有关模板的更多信息,请参阅 Helm 文档

在文件中,首先将 env 键添加到您的应用程序容器规范中,在 imagePullPolicy 键下方和 ports 上方:

~/node_project/nodeapp/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        env:
        ports:

接下来,将以下键添加到 env 变量列表中:

~/node_project/nodeapp/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        env:
        - name: MONGO_USERNAME
          valueFrom:
            secretKeyRef:
              key: MONGO_USERNAME
              name: {{ .Release.Name }}-auth
        - name: MONGO_PASSWORD
          valueFrom:
            secretKeyRef:
              key: MONGO_PASSWORD
              name: {{ .Release.Name }}-auth
        - name: MONGO_HOSTNAME
          valueFrom:
            configMapKeyRef:
              key: MONGO_HOSTNAME
              name: {{ .Release.Name }}-config
        - name: MONGO_PORT
          valueFrom:
            configMapKeyRef:
              key: MONGO_PORT
              name: {{ .Release.Name }}-config
        - name: MONGO_DB
          valueFrom:
            configMapKeyRef:
              key: MONGO_DB
              name: {{ .Release.Name }}-config      
        - name: MONGO_REPLICASET
          valueFrom:
            configMapKeyRef:
              key: MONGO_REPLICASET
              name: {{ .Release.Name }}-config        

每个变量都包含对其值的引用,在 Secret 值的情况下由 secretKeyRef 键 定义,对于 ConfigMap 值由 configMapKeyRef 定义。 这些键指向我们在上一步中创建的 Secret 和 ConfigMap 文件。

接下来,在 ports 键下,修改 containerPort 定义以指定容器上将暴露我们的应用程序的端口:

~/node_project/nodeapp/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
    ...
      env:
    ...
      ports:
        - name: http
          containerPort: 8080
          protocol: TCP
      ...

接下来,让我们修改默认包含在此部署清单中的活动性和就绪性检查。 这些检查确保我们的应用程序 Pod 正在运行并准备好为流量提供服务:

  • 就绪探测评估 Pod 是否准备好为流量提供服务,停止对 Pod 的所有请求,直到检查成功。
  • Liveness 探针检查基本的应用程序行为,以确定容器中的应用程序是否按预期运行和表现。 如果活性探测失败,Kubernetes 将重新启动容器。

有关两者的更多信息,请参阅 Architecting Applications for Kubernetes 中的 相关讨论

在我们的例子中,我们将构建 Helm 默认提供的 httpGet 请求 并测试我们的应用程序是否正在接受 /sharks 端点上的请求。 kubelet 服务 将通过向运行在应用程序 Pod 容器中的节点服务器发送 GET 请求并侦听端口 8080 来执行探测。 如果响应的状态码介于 200 和 400 之间,则 kubelet 将得出容器健康的结论。 否则,在状态为 400 或 500 的情况下,kubelet 将在就绪探测的情况下停止到容器的流量,或者在活性探测的情况下重新启动容器。

为 liveness 和 readiness 探针添加以下修改:

~/node_project/nodeapp/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
...
  spec:
    containers:
    ...
      env:
    ...
      ports:
        - name: http
          containerPort: 8080
          protocol: TCP
      livenessProbe:
        httpGet:
          path: /sharks
          port: http
      readinessProbe:
        httpGet:
          path: /sharks
          port: http

完成编辑后保存并关闭文件。

您现在已准备好使用 Helm 创建应用程序版本。 运行以下 helm install 命令,其中包括发布的名称和图表目录的位置:

helm install --name nodejs ./nodeapp

请记住,您可以首先使用 --dry-run--debug 选项运行 helm install,如 Step 3 中所述,检查为您的版本生成的清单。

同样,因为我们没有在 helm install 中包含 --namespace 标志,我们的图表对象将在 default 命名空间中创建。

您将看到以下输出,表明您的版本已创建:

OutputNAME:   nodejs
LAST DEPLOYED: Wed Apr 17 18:10:29 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME           DATA  AGE
nodejs-config  4     1s

==> v1/Deployment
NAME            READY  UP-TO-DATE  AVAILABLE  AGE
nodejs-nodeapp  0/3    3           0          1s

...

同样,输出将指示发布的状态,以及有关已创建对象的信息以及如何与它们交互。

检查 Pod 的状态:

kubectl get pods
OutputNAME                              READY   STATUS    RESTARTS   AGE
mongo-mongodb-replicaset-0        1/1     Running   0          57m
mongo-mongodb-replicaset-1        1/1     Running   0          56m
mongo-mongodb-replicaset-2        1/1     Running   0          55m
nodejs-nodeapp-577df49dcc-b5fq5   1/1     Running   0          117s
nodejs-nodeapp-577df49dcc-bkk66   1/1     Running   0          117s
nodejs-nodeapp-577df49dcc-lpmt2   1/1     Running   0          117s

一旦您的 Pod 启动并运行,请检查您的服务:

kubectl get svc
OutputNAME                              TYPE           CLUSTER-IP     EXTERNAL-IP       PORT(S)        AGE
kubernetes                        ClusterIP      10.245.0.1     <none>            443/TCP        96m
mongo-mongodb-replicaset          ClusterIP      None           <none>            27017/TCP      58m
mongo-mongodb-replicaset-client   ClusterIP      None           <none>            27017/TCP      58m
nodejs-nodeapp                    LoadBalancer   10.245.33.46   your_lb_ip        80:31518/TCP   3m22s

nodejs-nodeapp 服务关联的 EXTERNAL_IP 是您可以从集群外部访问应用程序的 IP 地址。 如果您在 EXTERNAL_IP 列中看到 <pending> 状态,这意味着您的负载均衡器仍在创建中。

在该列中看到 IP 后,在浏览器中导航到它:http://your_lb_ip

您应该看到以下登录页面:

现在您的复制应用程序正在运行,让我们添加一些测试数据以确保复制在副本集的成员之间正常工作。

第 6 步 — 测试 MongoDB 复制

随着我们的应用程序运行并通过外部 IP 地址访问,我们可以添加一些测试数据并确保它在我们的 MongoDB 副本集的成员之间被复制。

首先,确保您已将浏览器导航到应用程序登录页面:

单击获取鲨鱼信息按钮。 您将看到一个带有输入表单的页面,您可以在其中输入鲨鱼名称和对该鲨鱼一般特征的描述:

在表格中,添加您选择的初始鲨鱼。 为了演示,我们将 Megalodon Shark 添加到 Shark Name 字段,并将 Ancient 添加到 Shark Character 字段:

单击提交按钮。 您将看到一个页面,其中向您显示此鲨鱼信息:

现在点击顶部导航栏中的 Sharks 返回鲨鱼信息表单:

输入您选择的新鲨鱼。 我们将使用 Whale SharkLarge

单击 Submit 后,您将看到新的鲨鱼已添加到数据库中的鲨鱼集合中:

让我们检查一下我们输入的数据是否已经在我们的副本集的主要和次要成员之间复制。

获取您的 Pod 列表:

kubectl get pods
OutputNAME                              READY   STATUS    RESTARTS   AGE
mongo-mongodb-replicaset-0        1/1     Running   0          74m
mongo-mongodb-replicaset-1        1/1     Running   0          73m
mongo-mongodb-replicaset-2        1/1     Running   0          72m
nodejs-nodeapp-577df49dcc-b5fq5   1/1     Running   0          5m4s
nodejs-nodeapp-577df49dcc-bkk66   1/1     Running   0          5m4s
nodejs-nodeapp-577df49dcc-lpmt2   1/1     Running   0          5m4s

要访问 Pod 上的 mongo shell,您可以使用 kubectl exec 命令 和在 Step 2 中创建 mongo-secret 时使用的用户名]。 使用以下命令访问 StatefulSet 中第一个 Pod 上的 mongo shell:

kubectl exec -it mongo-mongodb-replicaset-0 -- mongo -u your_database_username -p --authenticationDatabase admin

出现提示时,输入与此用户名关联的密码:

OutputMongoDB shell version v4.1.9
Enter password: 

您将被放入一个管理 shell:

OutputMongoDB server version: 4.1.9
Welcome to the MongoDB shell.
...

db:PRIMARY>

虽然提示本身包含此信息,但您可以使用 rs.isMaster() 方法 手动检查哪个副本集成员是主副本集成员:

rs.isMaster()

您将看到如下输出,指示主节点的主机名:

Outputdb:PRIMARY> rs.isMaster()
{
        "hosts" : [
                "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
                "mongo-mongodb-replicaset-1.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
                "mongo-mongodb-replicaset-2.mongo-mongodb-replicaset.default.svc.cluster.local:27017"
        ],
        ...
        "primary" : "mongo-mongodb-replicaset-0.mongo-mongodb-replicaset.default.svc.cluster.local:27017",
        ...

接下来,切换到您的 sharkinfo 数据库:

use sharkinfo
Outputswitched to db sharkinfo

列出数据库中的集合:

show collections
Outputsharks

输出集合中的文档:

db.sharks.find()

您将看到以下输出:

Output{ "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 }
{ "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 }

退出 MongoDB Shell:

exit

现在我们已经检查了主节点上的数据,让我们检查它是否被复制到辅助节点。 使用以下命令将 kubectl exec 转换为 mongo-mongodb-replicaset-1

kubectl exec -it mongo-mongodb-replicaset-1 -- mongo -u your_database_username -p --authenticationDatabase admin

进入管理 shell 后,我们将需要使用 db.setSlaveOk() 方法来允许从辅助实例进行读取操作:

db.setSlaveOk(1)

切换到sharkinfo数据库:

use sharkinfo
Outputswitched to db sharkinfo

允许对 sharks 集合中的文档进行读取操作:

db.setSlaveOk(1)

输出集合中的文档:

db.sharks.find()

您现在应该会看到在主实例上运行此方法时看到的相同信息:

Outputdb:SECONDARY> db.sharks.find()
{ "_id" : ObjectId("5cb7702c9111a5451c6dc8bb"), "name" : "Megalodon Shark", "character" : "Ancient", "__v" : 0 }
{ "_id" : ObjectId("5cb77054fcdbf563f3b47365"), "name" : "Whale Shark", "character" : "Large", "__v" : 0 }

此输出确认您的应用程序数据正在副本集的成员之间复制。

结论

您现在已经使用 Helm 图表在 Kubernetes 集群上部署了一个复制的、高可用性的鲨鱼信息应用程序。 当您为应用程序构建自定义图表并利用 Helm 的 stable 存储库和 其他图表存储库 时,此演示应用程序和本教程中概述的工作流可以作为起点。

当您转向生产时,请考虑实施以下操作:

要了解有关 Helm 的更多信息,请参阅 Helm 简介、Kubernetes 包管理器如何使用 Helm 包管理器 在 Kubernetes 集群上安装软件和 Helm 文档