使用 Appsody 将 MERN 堆栈应用程序转换为云原生应用程序

本教程将展示如何使用开源 Appsody 项目将您现有的 MERN(MongoDB、ExpressJS、ReactJS 和 Node.js)堆栈应用程序转换为云原生应用程序。

具体来讲,我们将展示如何在 CloudNativeJS MERN 研讨会中使用 Appsody 的 nodejs-express 堆栈创建简单的待办事项列表(ToDo list)应用程序。通过使用 Appsody 堆栈,可以将所选的云技术和标准授权给该堆栈,从而确保在使用该堆栈的应用程序之间实现一致性和可靠性。

Appsody 简介

Appsody 是一个开源项目,可帮助您创建云原生容器化的应用程序。Appsody 应用程序会使用内置云原生功能(例如运行状况检查和用于 Prometheus 监视的指标)的预配置应用程序堆栈。使用 Appsody CLI,可以在部署到 Kubernetes 之前在本地运行、构建和测试应用程序。通过使用 Appsody,您不必精通基础容器技术,就可以开发能直接部署到 Kubernetes 的云原生应用程序。

前提条件

要完成本教程中的步骤,您需要:

您还需要克隆 MERN 堆栈应用程序:

git clone https://github.com/CloudNativeJS/mern-workshop.git
cd mern-workshop

将应用程序转换为 Appsody 应用程序

MongoDB 数据库

该应用程序使用的是 MongoDB 数据库,因此您需要使用以下命令来启动 MongoDB docker 容器:

docker pull mongo
docker run -d -p 27017:27017 --name mern-mongo mongo
export MONGO_URL=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mern-mongo)

这将创建一个 MongoDB 实例(用于侦听端口 27017),并设置环境变量 MONGO_URL(将在后端应用程序中使用)。

后端 Express 应用程序

首先为后端 Express.js 应用程序启用 Appsody:

cd backend
appsody init nodejs-express none

这将下载最新的 Appsody nodejs-express 堆栈,并创建一个 .appsody-config.yaml 文件。您可以使用此 yaml 文件来配置 Appsody 项目和堆栈的名称以及该项目中使用的版本。在撰写本文时,版本为 nodejs-express V0.4,但会经常发布更新。

现在应该已经为后端应用程序启用了 Appsody。但是,在将该应用程序与 Appsody 一起使用之前,您需要对代码进行一些调整。首先用下面显示的代码替换 server/routers/index.js 中的代码:

module.exports = function(options){
  const express = require('express');
  const app = express();

  require('./mongo')(app, options.server);

  return app;
};

Appsody 的 nodejs-express 堆栈中包含一个具备云原生功能(例如运行状况检查和监视)的预配置 Express.js 应用程序。您需要使用 module.exports 来导出自己的应用程序,以供该堆栈提供的应用程序使用。

接下来,在将这些功能授权给 Appsody 堆栈时,使用以下命令移除所有不再需要的重复文件:

rm -rf .dockerignore chart Dockerfile scripts public sever/server.js server/routers/health.js server/routers/public.js

移除了以下重复功能:

  1. appmetrics-dashappmetrics-prometheus。默认情况下,这些应用程序监视库已包含在 nodejs-express 堆栈中,因此不需要在应用程序中提供这些库。appmetrics-prometheus 提供一个 /metrics 端点,用于 Prometheus 监视。appmetrics-dash 在 /appmetrics-dash 中提供一个基于 Web 的仪表板,用于显示应用程序的性能指标。

  2. 现在,可以使用 options.server 来访问应用程序堆栈的 HTTP 服务器,而不需要使用 HTTP。

  3. 由于 nodejs-express 堆栈提供了 Pino 日志记录功能,因此移除了 log4js。

  4. 两个 app.use 都已被移除,因为它们是用于捕获所有未定义的路由并提供 404 错误。而 nodejs-express 堆栈已提供此功能。

现在已经对 index.js 进行了一些调整,接下来需要更新 package.json。首先添加一个主字段,用于定义 Appsody 应用程序的入口点。

{
  "name": "node-backend",
  "version": "1.0.0",
  "description": "Cool MERN app for Docker/Kubernetes",
  "private": true,
  "main": "server/routers/index.js",
  "engines": {
    "node": "^12.14.1"
  },
  "scripts": {
    "start": "node server/server.js",
    "build": "NODE_ENV=production webpack"
  },
  "dependencies": {
    "appmetrics-dash": "^5.3.0",
    "appmetrics-prometheus": "^3.1.0",
    "body-parser": "^1.17.1",
    "connect-mongo": "^3.2.0",
    "cors": "^2.8.4",
    "express": "^4.15.3",
    "log4js": "^3.0.5",
    "mongoose": "^5.8.11"
  }
}

您还可以移除 appmetrics-dash 和 appmetrics-prometheus 依赖项,因为它们已包含在 nodejs-express 堆栈中。

npm uninstall appmetrics-prometheus appmetrics-dash

该应用程序现在应该已完全兼容 Appsody,并且可以使用以下命令来启动:

appsody run  --docker-options "-e MONGO_URL"

该应用程序将在端口 3000 上运行。您已使用 --docker-options 选项将 MONGO_URL 环境变量传递给 Appsody。当该应用程序运行时,您所做的任何代码更改都会导致该应用程序重新启动,并且这些更改会立即反映到容器中。您还可以访问 Appsody nodejs-express 堆栈的其他云原生功能:

Appsody 端点:

应用程序定义的端点:

指标仪表板仅在开发期间可用,并且不包含在使用 appsody build 构建的镜像中。

将应用程序部署到 Kubernetes 中

现在已经将应用程序转换为使用 nodejs-express 堆栈,可以开始使用 appsody deploy 命令将微服务部署到 Kubernetes 集群中。要完成本教程中的 其余步骤,您将需要安装以下必备软件:

MongoDB 数据库

要将 MongoDB 数据库部署到 Kubernetes 中,可以使用 Helm,这是 Kubernetes 的软件包管理器,可用于将应用程序 Chart 安装到 Kubernetes 集群中。

首先将 Helm 配置为使用 stable Helm 存储库中的 Chart。

helm repo add stable https://kubernetes-charts.storage.googleapis.com

现在,可以使用稳定的 Helm Chart 将 MongoDB 部署到集群中。

helm install mongo --set replicaSet.enabled=true,service.type=LoadBalancer,replicaSet.replicas.secondary=3 stable/mongodb

运行 kubectl get all 命令,您应该会看到以下内容:

NAME                                    READY   STATUS    RESTARTS   AGE
pod/mongo-mongodb-arbiter-0             1/1     Running   0          21s
pod/mongo-mongodb-primary-0             0/1     Running   0          21s
pod/mongo-mongodb-secondary-0           0/1     Running   0          21s
pod/mongo-mongodb-secondary-1           0/1     Running   0          21s
pod/mongo-mongodb-secondary-2           0/1     Running   0          21s


NAME                             TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)           AGE
service/kubernetes               ClusterIP      10.96.0.1       <none>        443/TCP           7d1h
service/mongo-mongodb            LoadBalancer   10.102.160.21   <pending>     27017:32574/TCP   21s
service/mongo-mongodb-headless   ClusterIP      None            <none>        27017/TCP         21s


NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/appsody-operator-7ff45fd6cc   1         1         1       3d4h

NAME                                       READY   AGE
statefulset.apps/mongo-mongodb-arbiter     1/1     21s
statefulset.apps/mongo-mongodb-primary     0/1     21s
statefulset.apps/mongo-mongodb-secondary   0/3     21s

可能需要几分钟时间才能让所有 pod 都变为 1/1 READY 状态。可以使用 kubectl get pods --watch 来观察它们是否变为可用状态。一旦它们准备就绪,就可以继续执行本教程中的后续步骤。

后端 Express 应用程序

现在已经将 MongoDB 数据库部署到 Kubernetes 集群中,可以开始部署后端应用程序。在部署该应用程序之前,需要先修改连接 URL,因为部署的数据库会使用用户名和密码。 这意味着我们需要编辑 server/routers/mongo.js 文件中的 mongoConnect 变量并将其修改为以下内容。

let mongoConnect = `mongodb://${MONGO_CONFIG.mongoUser}:${MONGO_CONFIG.mongoPass}@${MONGO_CONFIG.mongoURL}:27017`;

运行以下命令以构建可用于生产环境的 Docker 镜像并生成部署清单文件。

appsody build

此时应该已在后端目录中创建了新文件 app-deploy.yaml。您可以使用此文件将该应用程序部署到 Kubernetes 中。在部署该应用程序之前,需要先向部署配置中添加几个环境变量。

为此,只需将数据库密码添加到空白值字段中,并将此 yaml 文件添加到 spec 字段下的部署配置中即可。

env:
  - name: MONGO_URL
    value: mongo-mongodb
  - name: MONGO_USER
    value: root
  - name: MONGO_PASS
    value:

要获取 MongoDB 数据库的密码,请运行以下命令:

echo $(kubectl get secret --namespace default mongo-mongodb -o jsonpath="{.data.mongodb-root-password}" | base64 --decode)

您的 app-deploy.yaml 文件的 spec 部分现在应如下所示:

spec:
  applicationImage: "dev.local/backend"
  createKnativeService: false
  env:
  - name: MONGO_URL
    value: mongo-mongodb
  - name: MONGO_USER
    value: root
  - name: MONGO_PASS
    value: XXXX
  expose: true
  livenessProbe:
    failureThreshold: 12
    httpGet:
      path: /live
      port: 3000
    initialDelaySeconds: 5
    periodSeconds: 2
  monitoring:
    labels:
      k8s-app: backend
  readinessProbe:
    failureThreshold: 12
    httpGet:
      path: /ready
      port: 3000
    initialDelaySeconds: 5
    periodSeconds: 2
    timeoutSeconds: 1
  service:
    annotations:
      prometheus.io/scrape: "true"
    port: 3000
    type: NodePort
  stack: nodejs-express
  version: 1.0.0

一般情况下,最好不要将此方法用于应保密的环境变量(例如密码)。对于这些环境变量,应使用 Kubernetes 密钥。为了简便起见,我们跳过了这一步。

现在使用以下命令将后端部署到 Kubernetes 集群中:

appsody deploy --no-build

我们在最后一步中使用 appsody build 构建可用于生产环境的 Docker 容器时,使用了 --no-build 选项,因此无需重新构建该容器。

完成部署后,您应该会看到如下消息。

Found deployment manifest /Users/andrewhughes/mern-blog/mern-workshop/backend/app-deploy.yaml
Using namespace default for deployment
Attempting to get resource from Kubernetes ...
Running command: kubectl get pods "-o=jsonpath='{.items[?(@.metadata.labels.name==\"appsody-operator\")].metadata.namespace}'" --all-namespaces
Attempting to get resource from Kubernetes ...
Running command: kubectl get deployments "-o=jsonpath='{.items[?(@.metadata.name==\"appsody-operator\")].metadata.namespace}'" -n default
Attempting to get resource from Kubernetes ...
Running command: kubectl get pod "-o=jsonpath='{.items[?(@.metadata.labels.name==\"appsody-operator\")].metadata.name}'" -n default
Attempting to get resource from Kubernetes ...
Running command: kubectl exec -n default -it appsody-operator-7ff45fd6cc-xzxwk -- /bin/printenv WATCH_NAMESPACE
Attempting to apply resource in Kubernetes ...
Running command: kubectl apply -f /Users/andrewhughes/mern-blog/mern-workshop/backend/app-deploy.yaml --namespace default
Appsody Deployment name is: backend
Running command: kubectl get rt backend -o "jsonpath=\"{.status.url}\"" --namespace default
Attempting to get resource from Kubernetes ...
Running command: kubectl get route backend -o "jsonpath={.status.ingress[0].host}" --namespace default
Attempting to get resource from Kubernetes ...
Running command: kubectl get svc backend -o "jsonpath=http://{.status.loadBalancer.ingress[0].hostname}:{.spec.ports[0].nodePort}" --namespace default
Deployed project running at http://localhost:31811

您的 Appsody nodejs-express 应用程序现在应该已成功部署到 Kubernetes 中。如果要在部署前端应用程序之前对其进行测试,请尝试对 http://localhost:31811/api/todos 发出包含以下主体的 POST 请求:

{
   "task": "appsody task",
   "author": "appsody"
}

或者使用 cURL:

curl -X POST -H 'Content-Type: application/json' -d "{\"author\":\"appsody\",\"task\": \"appsody task\"}" http://localhost:31811/api/todos

现在,如果您在浏览器中访问 http://localhost:31811/api/todos,您应该会看到刚刚发送的任务。注意,本教程内的 URL 中使用的端口号可能与您的 Appsody 应用程序的端口号不同。

前端 React 应用程序

要将前端应用程序部署到 Kubernetes 中,请使用 charts 目录中的 Helm Chart。这些 Helm Chart 是将应用程序部署到 Kubernetes 的预定义方式。在此例中,已经为该应用程序创建了这些 Helm Chart。通常,您必须编写自己的 Chart 以用于定义如何将应用程序部署到 Kubernetes 中。

在部署该应用程序之前,您需要先对 frontend 目录中 src/containers/App.js 文件中的 API_URL 变量进行小幅调整,然后才能使用部署到 Kubernetes 的后端应用程序的新端口。

const API_URL = 'http://localhost:BACKEND_PORT/api/todos';

使用以下命令来为前端应用程序构建 Docker 镜像:

cd frontend
docker build -f Dockerfile -t frontend:v1.0.0 .

现在已经构建了 Docker 容器,请通过 Helm 使用以下命令将其部署到 Kubernetes 中:

helm install frontend chart/frontend

您可以使用以下命令在 Kubernetes 中查看已部署的前端、后端和 MongoDB:

kubectl get pods

使用以下命令将应用程序移植到端口 30555

kubectl port-forward service/frontend-service 30555:80

现在应该可以通过 http://localhost:30555 访问前端应用程序

您现在已成功将您的 MERN 堆栈应用程序转换为 Appsody 应用程序并将其部署到 Kubernetes 中。如果您想了解有关 Appsody 的更多信息,请访问其网站或在 Slack 中与我们交谈。

本文翻译自:Make your MERN stack application cloud-native with Appsody(2020-07-13)