专栏文章 mock server 实践

孙高飞 · 2020年12月02日 · 最后由 瓜皮测试 回复于 2024年07月04日 · 23603 次阅读
本帖已被设为精华帖!

mock server 的一般玩法

  • mock server 的目的是一定程度上隔离待测系统,将依赖的其他服务 mock 掉后达到只测试待测系统的目的。
  • mock server 拦在待测程序和依赖服务中间, 接收来自待测程序法来的请求。 用户会在 mock server 中编写规则匹配模式, 当发来的请求匹配到规则后 (比如 header 中带有特殊的字段) 会返回事前 mock 好的 response。 如果没有命中规则,则会把请求转发给真实的服务进行处理。 这样做的目的有二: 1. 系统过于复杂, 要完全 mock 所有接口成本过高,所以这种模式可以只 mock 核心的,耗时长的接口。 2. 普通用户访问系统不受影响, 因为普通用户的 header 里没带那些特殊字段, 所以正常使用。 而需要 mock server 的特定程序在测试请求中加了特殊字段所以可以利用 mock server 的能力,这样使得一个环境可以对双方开放, 互不影响。
  • 测试程序可以实现一套 case,在连接 mock server 的时候可以测试,而当把 mock server 下掉后依然可以运行的目的。 当然这需要对 case 和 mock 都有相应的设计。 这样的目的实现该模块自己开发的时候先用 mock server 进行测试,但是后面集成时完成相应的集成测试。 当然也可以直接维护两套 case, 一套对 mock server 的一套对真实环境的。 看每个人的选择

mock server 的开源工具

介绍一个我喜欢用的开源 mock server: https://www.mock-server.com/mock_server/running_mock_server.html
mock server 的规则可支持 java, json,js 3 种格式。比如 java 风格的:

public class MockServer {
    public static void main(String[] args) {

        ClientAndServer server = new ClientAndServer("localhost", 8080, 1080);

        server.when(
                request()
                        .withMethod("GET")
                        .withPath("/hello/say")
        ).respond(
                response()
                        .withBody("mock successfully")
        );
        server.when(
                request()
                        .withMethod("GET")
                        .withPath("/test")
                        .withQueryStringParameters(
                                param("p", "1")
                        )
        ).respond(
                response()
                        .withCookie(new Cookie("cKey", "cValue"))
                        .withBody("test1")
        );

        server.when(
                request()
                        .withMethod("GET")
                        .withPath("/test")
                        .withQueryStringParameters(
                                param("p", "2")
                        )
        ).respond(
                response()
                        .withBody("test2")
        );
    }

}
  • 上面 new 一个 ClientAndServer("localhost", 8080, 1080) 就是在启动一个 mock server。 前两个参数是真实服务的 ip 地址和端口号, mock server 一旦发现有请求没有命中事前定义的规则, 那么就会转发给真实的服务处理。 而第三个参数就是 mock server 自己的端口号了。
  • 之后的程序都是规则匹配了, 可以编写规则拦截特定的 cookie,header,path,参数。 命中规则后就可以返回特定的 response, 并且可以设定延迟时间, 也就是可以设定延迟个几秒再返回请求, 这个对于需要测试网络延迟场景的 case 比较有用, 比如我们在混沌工程中注入特定接口的延迟故障,就是这么来的。

也可以是 json 格式的,比如启动 mock server 的时候直接指定读取哪个 json 文件中的规则。 例如:

java -Dmockserver.initializationJsonPath=${mock_file_path} -jar mockserver-netty-5.8.1-jar-with-dependencies.jar -serverPort 1080 -proxyRemotePort ${dport} -logLevel INFO

[
  {
    "httpRequest": {
      "path": "/simpleFirst"
    },
    "httpResponse": {
      "body": "some first response"
    }
  },
  {
    "httpRequest": {
      "path": "/simpleSecond"
    },
    "httpResponse": {
      "body": "some second response"
    }
  }
]
  • 上面代码第一行是启动命令, 在官网上下载 mock server 的 netty 包后, 可以指定规则文件的路径
  • 上面代码中的 json 就是规则文件。

具体的语法和规则请大家移步官网

图形界面

mock server 本身提供了一个图形界面来实时查看 mock server 接受到请求和规则命中情况。 如下:

动态加入规则

当 mock server 启动后,依然是可以通过 rest API 动态往里面加入规则的。 比如:

curl -v -X PUT "http://172.27.128.8:31234/mockserver/expectation" -d '{
  "httpRequest" : {
    "method" : "GET",
    "path" : "/view/cart",
    "queryStringParameters" : {
      "cartId" : [ "055CA455-1DF7-45BB-8535-4F83E7266092" ]
    },
    "cookies" : {
      "session" : "4930456C-C718-476F-971F-CB8E047AB349"
    }
  },
  "httpResponse" : {
    "body" : "some_response_body"
  }
}'

这就会给 mock server 中加入了一个新的规则, 可以在 mock server 的 UI 上看到

动态加入规则的目的是在一些比较复杂的情况下, 比如 mock server 跟部署工具或者环境绑定在一起, 手动起停比较困难的情况下,动态加入有利于作为 workround 和调式 mock 规则时使用。 当然 mock server 同样提供一个好用的功能, 它会把你动态加入的这些规则保存到一个文件中。 只需要你启动的时候加入两个参数就可以。 比如:

java -Dmockserver.persistExpectations=true -Dmockserver.persistedExpectationsPath=mockserverInitialization.json  -Dmockserver.initializationJsonPath=${mock_file_path} -jar mockserver-netty-5.8.1-jar-with-dependencies.jar -serverPort 1080 -proxyRemotePort ${dport} -logLevel INFO

上面的命令比之前多了两个参数分别是-Dmockserver.persistExpectations=true -Dmockserver.persistedExpectationsPath=mockserverInitialization.json 。 这两个参数保证了当你在 mocker server 上动态加入规则后, 这个规则能保存在这个文件里。 比如:

这种模式比较方便你调试 mock 规则。

抓取 response

有些时候研发的接口文档规范很差, 甚至研发自己都不知道要 mock 的 response 长什么样子。 所以我们需要能抓取到实际返回的接口请求。 那么 mock server 也提供了这样一个功能。 我们可以动态的调用一个 api。 如下:

curl -v -X PUT "http://172.27.128.8:31234/mockserver/retrieve?type=REQUEST_RESPONSES" -d '{
    "path": "/grid/console",
    "method": "GET"
}'

上面的代码是在从 mock server 中所有 method 为 GET 路径为/grid/console 的请求和相应详情。 效果如下:

在 k8s 中的玩法

在微服务架构中, 一次测试中可能会需要很多个 mock server, 以为一个 mock server 只能 mock 一个真实的服务。 那么如何部署这些 mock server 就是我这几天在研究的。 我们的产品是部署在 k8s 中的, 借鉴我们在混沌工程中进行故障注入的实践方式,这一次我同样选择使用 side car 模式向服务所在 POD 中注入 mock server 容器。 如下:

  • 第一步先注入一个 init container, init container 就是 pod 的初始化容器, 我们注入这个初始化容器是为了使用 iptables 来修改网络规则, 把原本应该发送给真实服务的请求转发给 mock server
  • 第二部注入 mock server 容器, 这个容器启动时接管了所有的流量, 命中规则返回 mock response, 没有命中规则的话转发给真实服务。

由于在 k8s pod 中的所有容器都是共享网络名称空间的, 所以这些容器都是天然的网络互通的 (用 localhost 就可以访问了). 通过编写这样的工具,就可以做到对产品的部署方式上进行无入侵的解决方案。

实现方式

  • 语言:golang
  • 包: k8s 开源的 client-go
package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "github.com/pkg/errors"
    log "github.com/sirupsen/logrus"
    "io/ioutil"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    yamlUtil "k8s.io/apimachinery/pkg/util/yaml"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
    "os"
    "strings"
    "time"
)

var (
    isRecover bool
    kubeConfigPath string
    configPath string
)

const (
    secretName = "mock-server-secret"
    secretFilePath = "pull-secret.yaml"
    timeout = 10
    initContainerName = "mock-init"
    mockServerContainerName = "mock-server"
    mockServerContainerImage = "reg.4paradigm.com/qa/mock-server"
)

func init() {
    log.SetOutput(os.Stdout)
    log.SetLevel(log.DebugLevel)
}

func main() {
    flag.BoolVar(&isRecover, "r", false, "是否将环境中pod的mock server移除")
    flag.StringVar(&kubeConfigPath, "-kubeconfig", "kubeconfig", "k8s集群的kubeconfig文件, 工具需要此文件连接k8s集群")
    flag.StringVar(&configPath, "-config", "config.json", "工具的配置文件的路径,工具会根据此配置文件中的内容向业务pod中注入mock server")
    flag.Parse()

    log.Info("init the kubeconfig")
    kubeConfig, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)
    if err != nil {
        log.Error("cannot init the kubeconfig")
        panic(err.Error())
    }
    log.Info("init the k8sclient")
    k8s, err := kubernetes.NewForConfig(kubeConfig)
    if err != nil {
        log.Error("cannot init the k8s client")
        panic(err.Error())
    }

    configs, err := readConfig(configPath)
    if err != nil {
        handleErr(err, "Failed to load config %s ", "config.json")
    }

    deploys := make(map[string]string)
    for _, c := range configs {
        if isRecover {
            log.Debugf("start to reset mock server ns[%s] deployment[%s]", c.Namespace, c.DeploymentName)
            err = reset(k8s, c.Namespace, c.DeploymentName)
            if err != nil {
                handleErr(err, "Failed to reset the mock server. deployment=%s namespace=%s", c.Namespace, c.DeploymentName)
            }
            deploys[c.DeploymentName] = c.Namespace
        } else {
            err = addSecrets(k8s,c.Namespace)
            if err != nil {
                handleErr(err, "Failed to add Secrets to the namespace %s", c.Namespace)
            }
            log.Debugf("start to inject mock server ns[%s] deployment[%s] dport[%s] mockfile[%s]", c.Namespace, c.DeploymentName, c.Dport, c.MockFilePath)
            err = injectMockServer(k8s, c.Namespace, c.DeploymentName, c.Dport, c.MockFilePath)
            if err != nil {
                handleErr(err, "Failed to setup the mock server. deployment=%s namespace=%s", c.Namespace, c.DeploymentName)
            }
            deploys[c.DeploymentName] = c.Namespace
        }
    }

    err = waitDeploymentReady(k8s, deploys)
    if err != nil {
        fmt.Printf("err: %+v\n", err)
        os.Exit(1)
    }

    log.Info("Done")
}

func handleErr(err error, message string, v ...interface{}) {
    err = errors.WithMessagef(err, message, v)
    fmt.Printf("err: %+v\n", err)
    os.Exit(1)
}

func reset(k8s *kubernetes.Clientset, ns string, deploymentName string) error {
    deployment, err := k8s.AppsV1().Deployments(ns).Get(deploymentName, metav1.GetOptions{})
    if err != nil {
        return errors.Wrap(err, "Failed to get deployment")
    }

    initContainers := deployment.Spec.Template.Spec.InitContainers
    for index, i := range initContainers {
        if i.Name == "mock-init" {
            initContainers = append(initContainers[:index], initContainers[index+1:]...)
        }
    }
    deployment.Spec.Template.Spec.InitContainers = initContainers

    Containers := deployment.Spec.Template.Spec.Containers
    for index, i := range Containers {
        if i.Name == "mock-server" {
            Containers = append(Containers[:index], Containers[index+1:]...)
        }
    }
    deployment.Spec.Template.Spec.Containers = Containers

    _, err = k8s.AppsV1().Deployments(ns).Update(deployment)
    if err != nil {
        return errors.Wrap(err, "Failed to update deployment")
    }

    return nil
}

func injectMockServer(k8s *kubernetes.Clientset, ns string, deploymentName string, dport string, mockFilePath string) error {
    deployment, err := k8s.AppsV1().Deployments(ns).Get(deploymentName, metav1.GetOptions{})
    if err != nil {
        return errors.Wrap(err, "Failed to get deployment")
    }

    initContainers := deployment.Spec.Template.Spec.InitContainers
    isExist := false
    for _, i := range initContainers {
        if i.Name == initContainerName {
            isExist = true
        }
    }
    //iptables -t nat -A PREROUTING -p tcp --dport 7777 -j REDIRECT --to-port 6666
    if !isExist {
        s := &corev1.SecurityContext{
            Capabilities: &corev1.Capabilities{
                Add: []corev1.Capability{"NET_ADMIN"},
            },
        }

        mockServerInitContainer := corev1.Container{
            Image:           "biarca/iptables",
            ImagePullPolicy: corev1.PullIfNotPresent,
            Name:            initContainerName,
            Command: []string{
                "iptables",
                "-t",
                "nat",
                "-A",
                "PREROUTING",
                "-p",
                "tcp",
                "--dport",
                dport,
                "-j",
                "REDIRECT",
                "--to-port",
                "1080",
            },
            SecurityContext: s,
        }
        initContainers = append(initContainers, mockServerInitContainer)
        deployment.Spec.Template.Spec.InitContainers = initContainers
    }

    containers := deployment.Spec.Template.Spec.Containers
    isExist = false
    for _, i := range containers {
        if i.Name == mockServerContainerName {
            isExist = true
        }
    }
    if !isExist {
        c := corev1.Container{
            Image:           mockServerContainerImage,
            ImagePullPolicy: corev1.PullAlways,
            Name:            mockServerContainerName,
            Env: []corev1.EnvVar{
                {
                    Name:  "mock_file_path",
                    Value: mockFilePath,
                },
                {
                    Name:  "dport",
                    Value: dport,
                },
            },
        }
        containers = append(containers, c)
        deployment.Spec.Template.Spec.Containers = containers
        deployment.Spec.Template.Spec.ImagePullSecrets = append(deployment.Spec.Template.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: secretName})

        _, err = k8s.AppsV1().Deployments(ns).Update(deployment)
        if err != nil {
            return errors.Wrap(err, "Failed to update deployment")
        }
    }
    return nil
}

type Config struct {
    Namespace string `json:"namespace"`
    //KubeConfigPath string   `json:"kubeconfig_path"`
    DeploymentName string `json:"deployment_name"`
    Dport          string `json:"dport"`
    MockFilePath   string `json:"mock_file_path"`
}

func readConfig(path string) ([]Config, error) {
    var config []Config

    data, err := ioutil.ReadFile(path)
    if err != nil {
        return nil, errors.Wrap(err, "ready file failed")
    }

    err = json.Unmarshal(data, &config)
    if err != nil {
        return nil, errors.Wrap(err, "unmarshal json failed")
    }
    return config, nil
}

func addSecrets(k8s *kubernetes.Clientset, ns string) error {
    _, err := k8s.CoreV1().Secrets(ns).Get(secretName, metav1.GetOptions{})
    if err != nil {
        if !strings.Contains(err.Error(), "not found") {
            return errors.Wrapf(err, "get secret %s", secretName)
        }else {
            log.Debugf("namespace[%s] has no secret, now create one", ns)
            var (
                err    error
                data   []byte
            )
            if data, err = ioutil.ReadFile(secretFilePath); err != nil {
                return errors.Wrapf(err, "read %s", secretFilePath)
            }
            if data, err = yamlUtil.ToJSON(data); err != nil {
                return errors.Wrap(err, "covert yaml to json")
            }
            se := &corev1.Secret{}
            if err = json.Unmarshal(data, se); err != nil {
                return errors.Wrap(err, "json unmarshal to secret")
            }
            //cluster := se.ObjectMeta.ClusterName
            //namespace := se.ObjectMeta.Namespace
            //seName := se.ObjectMeta.Name
            se.Namespace = ns
            if _, err = k8s.CoreV1().Secrets(ns).Create(se); err != nil {
                return errors.Wrap(err, "create secret failed")
            }
            return nil
        }
    }
    return nil
}


func waitDeploymentReady(k8s *kubernetes.Clientset, deploys map[string]string) error {
    now := time.Now()
    time.Sleep(time.Second * 1)
    for deploymentName, ns := range deploys {
        deploy, err := k8s.AppsV1().Deployments(ns).Get(deploymentName, metav1.GetOptions{})
        if err != nil {
            return errors.Wrapf(err, "Failed to get deployment[%s] ns[%s]", deploymentName, ns)
        }

        sumReplica := deploy.Status.UnavailableReplicas + deploy.Status.AvailableReplicas
        for deploy.Status.ReadyReplicas != *deploy.Spec.Replicas || sumReplica != *deploy.Spec.Replicas {
            if time.Now().Sub(now) > time.Minute*timeout {
                return errors.Wrapf(err, "deployment is not ready name:%s  ns:%s", deploymentName, ns)
            }

            deploy, err = k8s.AppsV1().Deployments(ns).Get(deploymentName, metav1.GetOptions{})
            if err != nil {
                return errors.Wrapf(err, "Failed to get deployment[%s] ns[%s]", deploymentName, ns)
            }
            time.Sleep(time.Second * 5)
            sumReplica = deploy.Status.UnavailableReplicas + deploy.Status.AvailableReplicas
            log.Debugf("Waiting: the deploy[%s] the spec replica is %d, readyRelicas is %d, unavail replica is %d, avail replica is %d",
                deploy.Name, *deploy.Spec.Replicas, deploy.Status.ReadyReplicas, deploy.Status.UnavailableReplicas, deploy.Status.AvailableReplicas)
        }
    }
    return nil
}

dockerfile:

FROM docker.4pd.io/base-image-openjdk8:1.0.1

RUN yum install -y git
RUN wget -O "mockserver-netty-5.8.1-jar-with-dependencies.jar" "http://search.maven.org/remotecontent?filepath=org/mock-server/mockserver-netty/5.8.1/mockserver-netty-5.8.1-jar-with-dependencies.jar"

EXPOSE 1080

ENTRYPOINT git clone https://xxxx:xxxx@xxxxxxxxx.git && cp mock-server-tools/${mock_file_path} .  && bash -x java -Dmockserver.persistExpectations=true -Dmockserver.persistedExpectationsPath=mockserverInitialization.json  -Dmockserver.initializationJsonPath=${mock_file_path} -jar mockserver-netty-5.8.1-jar-with-dependencies.jar -serverPort 1080 -proxyRemotePort ${dport} -logLevel INFO
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 23 条回复 时间 点赞

之前都是用 moco 来模拟,现在又学习了一种,很强大,感谢分享

mark 学习

对于模拟 3 方服务回调的 mock,以方便自动化 case 的运行,是使数据固定话好还是尽量动态化好呢

都行,看场景需要

陈恒捷 将本帖设为了精华贴 12月04日 00:20

命中规则返回 mock response, 没有命中规则的话转发给真实服务。

这个是这个 java 应用直接具备的功能,还是通过文中的 go 脚本实现的?看了下 go 脚本,貌似没见到有相关的逻辑。文中的 java mockserver 有提供 proxy 的方式,但也必须要在被测服务里加配置才能实现。

目前实际项目里,需要用到这种指定规则的 mock,非指定的走真实流量。找到的各种 mock 基本都是 mock 整个服务,或者需要到被测系统里加配置,缺少类似网关的实现方式自由选择哪些 mock 哪些直接转发,不大合适。

陈恒捷 #6 回复

是这个 mock server 直接具备的功能,不用二次开发, 它启动的时候可以设置 proxy 模式, 填写真实服务的 ip 地址和端口号。 我 go 的代码只是把 mock server 注入到了我们 k8s 集群里了,只做了这一个事而已。

看你的描述我觉得它的能力是符合你的需求的。 不过你们是什么场景需要这个能力的?

孙高飞 #7 回复

开发场景,客户端和服务端同步开发,客户端前期需要通过 mock 获取所需返回值。
测试场景也偶尔会用到,用于模拟返回特定错误码。

试用了下 port forwarding 的 proxy 方法,配置了 proxyRemotePort 和 proxyRemoteHost 。通过 mock-server 访问返回 404,但直接访问是可以的,看来还要再看看具体源码了。

陈恒捷 #8 回复

应该是配置错了吧,我这是可以路由的

陈恒捷 #8 回复

知道了,踩了暗坑,port forward 模式下转发时,一些和 host 有关的内容没有做替换,只是把内容做了转发。比如 header 里的 host 没改为 proxyRemoteHost 的值。所以背后真实服务看到 host 不大对,就不会正常返回了。

我实际启动命令里的相关配置:

java \
-Dmockserver.initializationJsonPath=${mock_file_path} \
-Dmockserver.watchInitializationJson=true \
-jar  mockserver-netty-5.11.1-jar-with-dependencies.jar -serverPort 1080 \
-proxyRemotePort 80 \
-proxyRemoteHost xxx.lizhi.fm

根据日志,转发时使用的 Url 和 header 中 host 字段还是 127.0.0.1:1080 ,不是我配置的 proxyRemote 相关信息。而且通过查服务端日志,会没有相关日志,估计是被框架层处理,都到不了业务逻辑层。

给请求加上 -H 'Host: xxx.lizhi.fm' 后,才能正常返回。详细的后面得再看看源码。

陈恒捷 #10 回复

还有这个坑呢。 我还没碰见😂

陈恒捷 #10 回复


这个坑,可以用框架的另外一个功能解决掉:定义一个全局的、优先级为-1 的、匹配规则为/* 的 override 期望,在转发之前重写请求,获取原请求的 host 或者其他请求头信息,添加到转发请求里,类似图中这样的。最近在用这个 apache mockserver 搭部门的 mock 平台,也遇到了这个问题。顺便强烈建议大佬尝试下这个框架的录制回放功能,贼好用。

我们的业务没有录制回放的需求, 所以没太研究, 你能分享一下么? PS: 创建这个期望的代码能贴一下么

孙高飞 #13 回复

public void setGlobalProxy(ServerClient server, String proxyAddress, HttpForward.Scheme scheme){

int port = scheme == HttpForward.Scheme.HTTPS?443:80;
boolean isSecure = scheme == HttpForward.Scheme.HTTPS;

Expectation proxyExpectation = new Expectation(
request().withPath("/.*")
).withPriority(-1).thenForward(
HttpOverrideForwardedRequest.forwardOverriddenRequest(
request().withHeader("Host",proxyAddress)
.withSocketAddress(proxyAddress,port)
.withSecure(isSecure)
)
);

server.upsert(proxyExpectation);
}
分享就有点不敢了,水平太差,大佬有兴趣的话,可以看下 apache mockserver 源码里的 example module,结合 mockserver-core module 的测试类来看体验更好,那里面有基本场景的代码样例。

个人使用 wiremock 比较多,看着功能上和 mock server 大同小异

建议有 mock 第三方服务需求的测试,上手 flask 吧,自己写个 mock 服务,丢到服务器上部署运行。你会打开一扇新的大门,从此你就会对这些已封装好的 mock 库呲之以鼻。不吹不黑,fastapi 也可以的,但我记得不支持 webservice。

17楼 已删除
18楼 已删除
20楼 已删除

ENTRYPOINT java -Dmockserver.persistExpectations=true -Dmockserver.persistedExpectationsPath=mockserverInitialization.json -Dmockserver.initializationJsonPath=mockserverInitialization.json -jar mockserver-netty-5.8.1-jar-with-dependencies.jar -serverPort 1080 -proxyRemotePort 8080 -logLevel DEBUG
============大佬,你好!上面的 dockerfile 是用来打 mockerserver 镜像的吧?请问:1、可以把 mockserver 的模拟文件 mockserverInitialization.json 挂载出来吗?这样方便动态修改。2、服务 A 的端口号是 8080,服务 B 的端口号是 8084,那么是不是要分别为它们打 mockserver 的镜像?

luckyhey #21 回复

问题 1: 可以的, 我的程序就是写个 demo,你可以把自己想参数化的东西都提取出来
问题 2:同问题 1 一样的,把端口号提取成环境变量就好了。

k8s 小白,请问下 几个问题:
1、是先创建 1 个 initcontainer,配置好 iptable 进行拦截转发,同时将 mockserver 镜像注入到这个 initcontainer 里面吗?initcontainer 不是启动之后就会销毁吗,这个 mockserver 还能运行?
2、如果不是将 mockerserver 注入到到这个 initcontainer 里面,新创建一个标准容器,initcontainer 里面的 iptable 将流量进行转发后 比如 8080 转发到 1080,此时不满足匹配规则,再放行到 8080,这样会不会进入死循环

我一个一个回答一下:

  1. mockserver 不是注入在 initcontainer 里的。是注入到 pod 里的一个单独的容器 。 initcontainer 只负责修改 iptables。 mockserver 容器则启动 mock 服务。
  2. 这个是 iptables 的规则。 你看我在设置规则的时候使用的是 prerouting 这个链。 如果网络包是从本主机发送出去的。 是不经过这个链的。只有外面的流量进入本主机的时候,才会经过这个链。 所以这个规则可以成功把外面发送给服务的流量转发给 mockserver,而 mockserver 本身发送给服务请求不会被这个规则拦截(mockserver 容器和服务容器都是在 Pod 中的,在 k8s 中同一个 pod 中的所有容器共用同一个网络名称空间。所以对于目标服务来说,mockserver 容器其实就是本机网络)。所以不会造成死循环。 具体的规则你看一下我下面在网上找的文章的截图:

孙高飞 #24 回复

谢谢,已经测试成功,多谢大神😀

大佬,这个 god 代码是用来干嘛的? 看不懂 go

XQ #26 回复

为了把 mock server 注入到目标 pod 用的

大佬好,看了下你这边的方案,mock-server 是部署在了需要被 mock 掉的服务?不是我们本身的后端服务里面?这样假设我们的后端需要 mock 多个服务的接口,那么就需要在每一个依赖服务里面去启动 mock-server?有没有啥方案可以利用 sidecar ,在我们自身的后端服务内搞一个 mock-server 可以搞定的?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册