• 还在篮子里

    分布式应用运行时 Dapr X Kubernetes · 语雀

    分布式应用运行时 Dapr X Kubernetes

    前言

    我前不久体验了 Dapr 的单机使用,体验不错。不过Dapr 作为一个分布式应用的运行时,当然需要加入集群使用才能完全体验呀。我们使用 GitHub 上的文档 部署。首先我们需要一个 Kubernetes 集群来做实验,选择 Rancher 由于我比较熟悉 Rancher ,同时 UI 操作才能让人感受到工具的便利嘛。之后会使用 RancherHelm 部署 DaprRedis 图表,不过由于 UI 操作的便利,点一下就部署了。

    集群的环境部署比较长,不过本来这些工具就是部署之后一劳永逸降低心智负担,当然你有一个 Kubernetes 可以直接实验就更好了。

    下面是 架构 图

    image.png

    可以看到我们已经接触到了 Dapr 多语言的特性了。两个 Pod 包括不同语言的应用:Python 和 Node.js ,他们通过 Dapr Injects 的 sidecar 交互。

    那么我们开始吧!

    环境

    Kubernetes 部署

    Docker

    1$ yum install docker-ce-18.06.2.ce-3.el7 -y
    此处为语雀文档,点击链接查看:https://www.yuque.com/opte0v/shu93f/gss3ca

    Rancher

    1$ sudo docker run -d --restart=unless-stopped -p 80:80 -p 443:443 rancher/rancher:latest
    此处为语雀文档,点击链接查看:https://www.yuque.com/opte0v/shu93f/td5aq3

    K8S By Rancher

    此处为语雀文档,点击链接查看:https://www.yuque.com/opte0v/shu93f/k8sbyrancher

    你也可以使用其他的工具快速启动 Kubernetes 集群

    可选部署 Kubernetes 方法

    Dapr

    1. Using Helm(Advanced)

    添加 Dapr 源 https://daprio.azurecr.io/helm/v1/repo 到商店

    image.png

    1. 启动 Dapr

    image.png

    1. 默认配置即可

    image.png

    1. 等待一段时间完成

    image.png

    你可以选择其他方法安装 Dapr

    Using Dapr CLI【可选】官方文档

    Redis

    1. 同样在 Rancher 的商店中找到 Redis Chart,默认部署即可。(可以自己设定一些选项比如密码和访问端口等方便你接下来的操作)

    image.png

    1. 等待一会儿部署完毕

    image.png

    1. 使用 Rancher 自带的 kubectl 即可执行命令

    image.png

    1. 使用 以下命令获取到 redis 命名空间下设置的密码

    kubectl get secret --namespace redis redis -o jsonpath="{.data.redis-password}" | base64 --decode

    image.png

    可以看到密码是 123456(方便实验,内网环境,生产环境请使用生成密码)

    1. 找到 Redis 的访问端口,我的是 192.168.0.116:32091(这是随机的端口)

    image.png

    1. 接下来我们把这些值置换到官方给的模板里的

    Configuration

    这里是明文存储的 secret ,生产环境请参照.secret management .

    Configuring Redis for State Persistence and Retrieval

    在刚才的 kubectl 命令行中继续操作。

    1$ vi redis-state.yaml

    记得将上文 4 ,5 步骤得到的 HOST 地址 和 Password 密码替换.

     1apiVersion: dapr.io/v1alpha1
     2kind: Component
     3metadata:
     4  name: statestore
     5spec:
     6  type: state.redis
     7  metadata:
     8  - name: redisHost
     9    value: <HOST>
    10  - name: redisPassword
    11    value: <PASSWORD>

    同时这个文件也可以在 samples/2.hello-kubernetes/deploy 下找到

    我的 Yaml 文件

    Apply the configuration

    使用 kubectl apply 部署。

    1$ kubectl apply -f redis-state.yaml
    2$ kubectl apply -f redis-pubsub.yaml

    HelloKubernetes

    Download Code

    首先下载示例代码

    1$ git clone https://github.com/dapr/samples.git
    2$ cd samples/2.hello-kubernetes/node

    Node

    该示例有多个应用,首先我们看老朋友 Node.js 的代码

    Cat app.js

     1// $ cat app.js 
     2
     3const express = require('express');
     4const bodyParser = require('body-parser');
     5require('isomorphic-fetch');
     6
     7const app = express();
     8app.use(bodyParser.json());
     9
    10const daprPort = process.env.DAPR_HTTP_PORT || 3500;
    11const daprUrl = `http://localhost:${daprPort}/v1.0`;
    12const port = 3000;
    13
    14app.get('/order', (_req, res) => {
    15    fetch(`${daprUrl}/state/order`)
    16        .then((response) => {
    17            return response.json();
    18        }).then((order) => {
    19            res.send(order);
    20    });
    21});
    22
    23app.post('/neworder', (req, res) => {
    24    const data = req.body.data;
    25    const orderId = data.orderId;
    26    console.log("Got a new order! Order ID: " + orderId);
    27
    28    const state = [{
    29        key: "order",
    30        value: data
    31    }];
    32
    33    fetch(`${daprUrl}/state`, {
    34        method: "POST",
    35        body: JSON.stringify(state),
    36        headers: {
    37            "Content-Type": "application/json"
    38        }
    39    }).then((response) => {
    40        console.log((response.ok) ? "Successfully persisted state" : "Failed to persist state");
    41    });
    42
    43    res.status(200).send();
    44});
    45
    46app.listen(port, () => console.log(`Node App listening on port ${port}!`));

    可以看到这个和我们之前使用的单机的 Node.js 一样,可以参照我的这篇文章

    Node Application Explain

    此处为语雀文档,点击链接查看:https://www.yuque.com/abser/process/fa0ntp#zjdNP

    Deploy Node Application

    虽然代码一样,但是我们部署的方法变了,不是直接使用 dapr 进行部署,而是使用 kubectl(注意我们当前目录还在 samples/2.hello-kubernetes/node

    1$ kubectl apply -f ../deploy/node.yaml
    2
    3service/nodeapp created
    4deployment.apps/nodeapp created

    如果你好奇,我们可以看一看 node.yaml 的内容(我把代码块调小了,可以自己滑动查看)

     1kind: Service
     2apiVersion: v1
     3metadata:
     4  name: nodeapp
     5  labels:
     6    app: node
     7spec:
     8  selector:
     9    app: node
    10  ports:
    11  - protocol: TCP
    12    port: 80
    13    targetPort: 3000
    14  type: LoadBalancer
    15
    16---
    17apiVersion: apps/v1
    18kind: Deployment
    19metadata:
    20  name: nodeapp
    21  labels:
    22    app: node
    23spec:
    24  replicas: 1
    25  selector:
    26    matchLabels:
    27      app: node
    28  template:
    29    metadata:
    30      labels:
    31        app: node
    32      annotations:
    33        dapr.io/enabled: "true"
    34        dapr.io/id: "nodeapp"
    35        dapr.io/port: "3000"
    36    spec:
    37      containers:
    38      - name: node
    39        image: dapriosamples/hello-k8s-node
    40        ports:
    41        - containerPort: 3000
    42        imagePullPolicy: Always
    

    注意这两个选项,    dapr.io/enabled: "true" 告诉 Dapr 控制器注入边车

     dapr.io/id: "nodeapp" 告诉 Dapr 这个部署的唯一 ID(用来 Dapr 之间相互通信)

    现在我们来查看状态和外部 IP(外部 IP 在 pending 是因为我们使用 Rancher 的原因,4 层负载均衡并不是每一个运营商都支持,不过外部 IP 不影响本次 Demo, 我会在下面做一个 Ingress 访问它

    1$ kubectl get svc nodeapp
    2NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
    3nodeapp   LoadBalancer   10.43.255.238   <pending>     80:30557/TCP   5m17s

    将 NodeApp 导入环境变量【跳过:由于我们没有 EXTERNAL-IP 】

    1$ export NODE_APP=$(kubectl get svc nodeapp --output 'jsonpath={.status.loadBalancer.ingress[0].ip}')

    Python

    我们还有一个 Python 的 Application 需要部署。

    1$cd samples/2.hello-kubernetes/python 
    2$cat app.py

    Python 的代码很简单,他把 JSON 消息发到 localhost:3500 (这个端口是可变的,3500Dapr 默认端口) 调用 Node App 的 neworder endpoint 。我们的消息也是很简单的每秒递增一次的消息。

     1import time
     2import requests
     3import os
     4
     5dapr_port = os.getenv("DAPR_HTTP_PORT", 3500)
     6dapr_url = "http://localhost:{}/v1.0/invoke/nodeapp/method/neworder".format(dapr_port)
     7
     8n = 0
     9while True:
    10    n += 1
    11    message = {"data": {"orderId": n}}
    12
    13    try:
    14        response = requests.post(dapr_url, json=message)
    15    except Exception as e:
    16        print(e)
    17
    18    time.sleep(1)

    部署并等待 Pod 进入 Running 状态

    1$ kubectl apply -f ./deploy/python.yaml
    2$ kubectl get pods --selector=app=python -w
    3
    4NAME                        READY   STATUS              RESTARTS   AGE
    5pythonapp-b5fd4474d-tk84x   0/2     ContainerCreating   0          9s
    6pythonapp-b5fd4474d-tk84x   1/2     ErrImagePull        0          38s
    7pythonapp-b5fd4474d-tk84x   1/2     ImagePullBackOff    0          39s
    8pythonapp-b5fd4474d-tk84x   2/2     Running             0          3m24s

    Observe

    我们毕竟使用的是 UI 界面,现在我们可以通过 Rancher UI 检验一下我们的成果。

    可以看到 nodeapppythonapp 两个 Pod 都在 active 状态。同时我们点进入更多信息界面可以看到 Pod 中还有 Dapr 的 边车

    image.png

    我们进入 nodeapp 检查日志

    image.png

    或者使用命令行

    1$ kubectl logs --selector=app=node -c node
    2
    3Got a new order! Order ID: 1
    4Successfully persisted state
    5Got a new order! Order ID: 2
    6Successfully persisted state
    7Got a new order! Order ID: 3
    8Successfully persisted state

    Persistence

    当然,我们不能使用 LoadBalance 或者 外部 IP 并不能阻止我们检查持久性。

    我们可以通过 Rancher 添加一个 Ingress 七层负载均衡到 目标容器进行访问。

    image.png

    然后设置 nodeapp 的端口 3000

    image.png

    等待一会儿出现分配的地址 http://nodeapp.default.192.168.0.102.xip.io

    image.png

    浏览器或者命令行访问,会返回最新的 orderID

    1$ curl http://nodeapp.default.192.168.0.102.xip.io/order
    2{"orderID":"42"}

    Clean

    这会清理所有的东西,包括 状态组件。

    1$ cd samples/2.hello-kubernetes/deploy
    2$ kubectl delete -f .

    image.png

    Summary

    首先使用 Dapr 的开发感受还需要一步,就是修改代码之后编译为自己的 Docker 然后部署在 Kubernetes 上,这里不详述,留给读者当练手即可。引用官方的 Next Steps 大概导引一下。

    Next Steps

    Now that you’re successfully working with Dapr, you probably want to update the sample code to fit your scenario. The Node.js and Python apps that make up this sample are deployed from container images hosted on a private Azure Container Registry. To create new images with updated code, you’ll first need to install docker on your machine. Next, follow these steps:

    1. Update Node or Python code as you see fit!
    2. Navigate to the directory of the app you want to build a new image for.
    3. Run docker build -t <YOUR_IMAGE_NAME> . . You can name your image whatever you like. If you’re planning on hosting it on docker hub, then it should start with <YOUR_DOCKERHUB_USERNAME>/.
    4. Once your image has built you can see it on your machines by running docker images.
    5. To publish your docker image to docker hub (or another registry), first login: docker login. Then rundocker publish <YOUR IMAGE NAME>.
    6. Update your .yaml file to reflect the new image name.
    7. Deploy your updated Dapr enabled app: kubectl apply -f <YOUR APP NAME>.yaml.

    这一次的 Dapr 体验遇到的一些问题主要是集群本身的网络问题。本身 Dapr 在这个 Demo 中起到的就是通信和存储状态的服务,但是在实际使用中并没有看到非常多的日志可供调试。也就是说依旧需要专业的运维人员进行 Dapr 开发的维护。不过,本来 Dapr 的打算就是制定标准,划开开发人员和运维人员的界限降低心智负担。

    Demo 中是两个语言的应用,我在查看日志的时候看了下环境变量,看到了 GRPC 的 50001 端口。同时 Dapr 也提供 各个语言的 SDK。我们看过了两个应用的代码,都很简单,不过可以看到的是对于通信的感知是不大的,或者说对于 Dapr 的感知不大,意味着微服务改为 Dapr 的并不需要改动太多,就是服务的 URL 变动一下。剩余的交给 Dapr 的 Sidecar 就行了。

    如果你不选择 Rancher 作为 搭建 Kubernetes 的集群的工具的话,跳转 也有其他的教程指导。没有一些非常明白好看的 UI 了。Rancher 的可视化部署状态在这个 Demo 中起到了很大的作用。

    接下来对于 Dapr 会尝试一下 他的事件驱动特性和 Pub/Sub 。这对于分布式应用来说也比较好玩。我非常赞同边车的开发模式,说到 sidecar 很多人都会想到 Istio 和 envoy,我也是一开始因为 ServiceMesh 和 sidecar 才开始关注 Dapr 的。刚看到开源的消息到现在,GitHub stars 已经到 4k 了,不过一天时间涨了 1k 多。微软的能量还是挺大的。

    Refer

    来源: 分布式应用运行时 Dapr X Kubernetes · 语雀