Think Deep,Work Lean

微服务调试的几种方式

Posted on By zack

背景

https://github.com/kubesphere/kubesphere/issues/3554#event-4499512886

istio bookinfo微服务中,reviews v2会访问ratings服务;ratings v2默认使用mongodb存储数据;

根本原因是ratings服务有报错,导致reviews在访问ratings服务时超时失败,而reviews对返回的数据处理不够,导致reviews访问出错。

现在将服务全部更新到最新镜像 docker.io/istio/examples-bookinfo-reviews-v1:1.16.3;发现productpage无法正常访问。

/ # curl productpage:9080/productpage?u=test%
    </div>
    <div class="col-md-6">
      <h4 class="text-center text-primary">Error fetching product reviews!</h4>
      <p>Sorry, product reviews are currently unavailable for this book.</p>

    </div>
  </div>
</div>

老的问题,没有解决,新的问题又来了。

其实将productpage的镜像改成低版本docker.io/istio/examples-bookinfo-reviews-v1:1.16.2;ratings改成高版本 1.16.3或是release,都会正常。

但是稀里糊涂绕过去了,总不痛快,现在结合源码,做一次完全的定位与分析。

复现

直接部署微服务,由于这个问题是代码问题,与istio无关,不用注入sidecar。

kubectl apply -f https://raw.githubusercontent.com/istio/istio/89850ae760e3fefb80955b3318a0d41b633dd09f/samples/bookinfo/platform/kube/bookinfo.yaml

➜  ~ k get po
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-5f449bdbb9-f7b4x       1/1     Running   0          16h
productpage-v1-5598888b56-86n8p   1/1     Running   0          127m
ratings-v1-857bb87c57-hqsxn       1/1     Running   0          16h
reviews-v2-5d56c488f5-mxwnf       1/1     Running   0          16h

此时访问Productpage就会出现上面的错误 Sorry, product reviews are currently unavailable for this book.

检查product日志,发现并没有请求reviews服务:

➜  istio git:(master2) ✗ k logs -f deploy/productpage-v1 -c productpage
INFO:root:start at port 9080
 * Serving Flask app "productpage" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
INFO:werkzeug: * Running on http://0.0.0.0:9080/ (Press CTRL+C to quit)
INFO:werkzeug: * Restarting with stat
INFO:root:start at port 9080
WARNING:werkzeug: * Debugger is active!
INFO:werkzeug: * Debugger PIN: 257-177-282
INFO:werkzeug:10.233.70.169 - - [30/Mar/2021 00:00:37] "GET /productpage?u=test HTTP/1.1" 200 -
INFO:werkzeug:10.233.70.169 - - [30/Mar/2021 00:00:53] "GET /productpage?u=test HTTP/1.1" 200 -

默认开启的debug日志,会打印所有的http请求。

我们直接看它的代码,就是发个http请求,http://reviews:9080/reviews/0

https://github.com/istio/istio/blob/81fe6075722b5a083743ee6d7180daff1bb77f93/samples/bookinfo/src/productpage/productpage.py#L382

def getProductReviews(product_id, headers):
    # Do not remove. Bug introduced explicitly for illustration in fault injection task
    # TODO: Figure out how to achieve the same effect using Envoy retries/timeouts
    for _ in range(2):
        try:
            url = reviews['name'] + "/" + reviews['endpoint'] + "/" + str(product_id)
            res = requests.get(url, headers=headers, timeout=3.0)  # http请求
        except BaseException
            res = None
        if res and res.status_code == 200:
            return 200, res.json()
    status = res.status_code if res is not None and res.status_code else 500
    return status, {'error': 'Sorry, product reviews are currently unavailable for this book.'}

为什么这个http请求没有响应的呢?是网络?还是什么?

进productpage这个pod发现无法使用常用的所有的命令 curl dig wget nslookup ping,也无法装软件。

进productpage这个pod 的ns后,主动curl 这个服务的ip地址+路径,能正常返回。

通过dns,能正常解析reviews服务。

在同一个namespace手动起一个alpine的pod作为客户端,来请求reviews服务,正常返回。

kubectl run alpine -it --image=alpine --restart=Never --rm

一切看似正常,代码也正常,但就是在productpage使用代码访问时就不对,而且这个Pod里面不能手动模拟请求。。。what a fuck!!

调试

现在想使用master分支代码来调试下服务。

该如何调试呢?

  • 1.方案一:在本地运行服务后,使用telepresence swap线上的deployment。

但是在mac本地跑这个py时,有些包安装出错,没有继续去折腾。换下个方案

  • 2.方案二:直接在Linux上跑这个py,然后使用external service方式在k8s内部使用该外部服务。
export ip=192.168.0.12

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: test-productpage
  namespace: default
spec:
  ports:
  - name: http
    port: 9080
---
apiVersion: v1
kind: Endpoints
metadata:
  name: test-productpage
  namespace: default
subsets:
- addresses:
  - ip: ${ip}
  ports:
  - name: http
    port: 9080
    protocol: TCP
EOF

然后访问的时候正常,打了些断点,手动打印debug日志,都符合预期。

  • 3.方案三:build镜像,并更新

由于每次会把所有的镜像全部构建一次,所有有点慢,且在本地打镜像受网络影响严重。

curl https://raw.githubusercontent.com/istio/istio/e622dc31d88de79074536c65c54a690c06afff56/samples/bookinfo/build_push_update_images.sh 

bash build_push_update_images.sh debug --prefix=zackzhangkai

更新上去后,productpage也是正常的。

  • 4.方案四:更改productpage的镜像为alpine,并提权
FROM alpine

COPY requirements.txt ./
RUN apk add --update python3 py3-pip python3-dev gcc musl-dev
RUN pip3 install -r requirements.txt

RUN ln -s /usr/bin/python3 /usr/bin/python
COPY test-requirements.txt ./
RUN pip3 install --no-cache-dir -r test-requirements.txt

COPY productpage.py /opt/microservices/
COPY tests/unit/* /opt/microservices/
COPY templates /opt/microservices/templates
COPY static /opt/microservices/static
COPY requirements.txt /opt/microservices/

ARG flood_factor
ENV FLOOD_FACTOR ${flood_factor:-0}

EXPOSE 9080
WORKDIR /opt/microservices
RUN python -m unittest discover

CMD ["python", "productpage.py", "9080"]

更改为alpine基础镜像后,可以随意安装想用的软件,也更方便调试了。

分析

说明线上最新的版本,其实并不是Master的代码。

当某一个镜像中的代码不正常的时候,怎么办?

在linux一切皆文件,包括环境变量、进程信息等,在linux上都可以看到。

首先把productpage更新到有问题的镜像(Istio官方最新镜像,目前是Latest镜像)。

找出该Pod的docker -> 进程PID —> cd /proc/$PID/cwd

此时可以看到该进程的运行目录。把它的代码拷贝到本地,与master代码对比:

可以看到引用了xxxPORTS变量。

cat environ 发现有这个变量,但是显示不对,到此真相大白:

现在明白了,原来是这个PORT不对,造成没有发送http请求,也就没有被http Debug日志记录。