# 记一次酣畅淋漓的 K8s Ingress 排错过程(302,404,503,...)


- [故事开始](#故事开始)
- [第 1 关：【流量重定向到 /】](#第-1-关流量重定向到-)
- [第 2 关：【应用返回 302，重定向到 /，引入 503 错误】](#第-2-关应用返回-302重定向到-引入-503-错误)
- [第 3 关：【静态资源访问遇到 503 问题】](#第-3-关静态资源访问遇到-503-问题)
- [第 4 关：【静态资源访问遇到 403 问题】](#第-4-关静态资源访问遇到-403-问题)
- [第 5 关：【WebSocket close with status code 1006】](#第-5-关websocket-close-with-status-code-1006)
- [最后效果](#最后效果)

## 故事开始

如果你配置过 Ingress，那你一定遇到过各种各样的坑。

如果你尝试在 Kubernetes 集群里部署 [OpenVSCode Server](https://github.com/gitpod-io/openvscode-server)，并且通过 Ingress 暴露服务，那…… 祝你好运。

这个应用默认监听 `/` 路径，并且不支持配置修改。此外这个服务还用到了 302 重定向，Set Cookie，Websocket 等。或许你已经猜到了问题的复杂性……

于是，当你尝试通过 `/xxx` 在 Ingress 走七层路由访问这个应用的时候，酸爽的体验就开始了……

## 第 1 关：【流量重定向到 /】

首先我们会在 Ingress 配置里写上类似下列配置内容，将 `/vscode` 流量转发到具体的 Service：

```yaml
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: vscode-01
            port:
              number: 8888
        path: /vscode
        pathType: Prefix
```

这时候尝试访问应用，你会收获 404：

![](1.png)

404 说明流量已经到了应用，但是路径不对。稍加思考，其实是 Pod 收到了一个路径为 `/vscode` 的 http 请求，但是 Pod 期待的第一个请求路径是 `/`，404 合情合理。

**【解决方案】**

这时候你可以在 Ingress 的 Annotation 里加上这样一行：

```yaml
nginx.ingress.kubernetes.io/rewrite-target: /
```

## 第 2 关：【应用返回 302，重定向到 /，引入 503 错误】

当配置了 rewrite 实现流量重写到 `/` 路径后，继续尝试访问应用，可以得到如下错误礼包：

![](2.png)

浏览器上首先看到的是 503 错误，但是第一个请求不应该 503。进一步看可以发现服务端先响应了一个 302，将请求重定向到了 `/` 路径，进而第二个请求发到了 `/`，在 Ingress 这一层被拦截了，返回 503。

**【解决方案】**

可以在 Ingress 里继续加两行配置，实现 302 Location 的修改：

```yaml
nginx.ingress.kubernetes.io/configuration-snippet: |
  more_set_headers "Location: $scheme://$http_host/vscode/";
```

## 第 3 关：【静态资源访问遇到 503 问题】

解决了 302 重定向到 `/` 的问题之后，继续尝试访问应用，很可惜，还有坑等着：

![](3.png)

可以看到 302 的下一个请求是 200，问题不大。接着的 503 是什么问题呢？

![](4.png)

看来前端需要加载一些在 `/stable-xxx/` 下的资源，这个请求在 Ingress 这一层过不去，再次 503 了。

因为我们的 Ingress 配置了全局的 `rewrite-target: /`，所以 Pod 无论如何收不到路径为 `/stable-xxx/` 的请求，也就是当前 Ingress 里咋配置都是徒劳了，这个问题解决不了。

**【解决方案】**

新增一个 Ingress，处理 `/stable-xxx/` 流量：

```yaml
spec:
  rules:
  - http:
      paths:
      - path: /stable-487e0b6eb726a84faf6b1a95c68a092fba078fd1/static/(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: vscode-01
            port:
              number: 8888
```

## 第 4 关：【静态资源访问遇到 403 问题】

解决了 503 问题后，继续尝试访问应用，这会会继续遇到 403 错误：

![](5.png)

这一步的定位思路稍微有点绕了，我首先尝试用 `kubectl port-forward` 将 Pod 流量直接暴露出来，然后尝试访问，发现一切正常。接着用 nsenter 进了对应 Pod 的网络命名空间，再通过 tcpdump 尝试抓包分析 http 请求流量，发现 Ingress 方式的请求在 403 这一步少了 Cookie。

进一步分析请求，可以看到：

![](6.png)

也就是第一次请求应用的时候，应用的响应头里带了 `Set-Cookie: vscode-tkn=eomdZNpW;`，因此第二次浏览器请求 `/vscode/xxx` 的时候就会带上这个 Cookie，但是静态资源在 `/stable-xxx/static` 下，由于路径不一致（也不是包含关系），因此第二次请求的 Cookie 就没了。

**【解决方案】**

因为这个应用不支持配置路径，又是 TS 写的，我也不懂 TS 代码，所以我还是考虑能从 Ingress 角度入手。

既然静态资源都在 `/stable-xxx/static` 下，那干脆将 `/vscode` 改成 `/stable-xxx/`。

```yaml
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: vscode-01
            port:
              number: 8888
        path: /stable-487e0b6eb726a84faf6b1a95c68a092fba078fd1/$
        pathType: ImplementationSpecific
```

为了让正则表达式正常工作，还得加这样一行 Annotation：

```yaml
nginx.ingress.kubernetes.io/use-regex: "true"
```

此时应用访问地址也就从：
- `/vscode/?tkn=eomdZNpW`
更新成了：
- `/stable-487e0b6eb726a84faf6b1a95c68a092fba078fd1/?tkn=eomdZNpW`

注意，Annotation 里的 `configuration-snippet` 这时候需要同步更新成：

```yaml
nginx.ingress.kubernetes.io/configuration-snippet |
  more_set_headers "Location: $scheme://$http_host/stable-487e0b6eb726a84faf6b1a95c68a092fba078fd1/";
```

这时候 Cookie Path 相关的 403 问题应该就能绕过去了。

## 第 5 关：【WebSocket close with status code 1006】

经过前面一顿改之后，现在请求应用，已经几乎接近真相了：

![](7.png)

看起来是 Websocket 相关的错误。继续仔细看下 http 信息，可以找到如下一个请求：

![](8.png)

我们一开始配置的 path 规则是匹配 `/stable-487e0b6eb726a84faf6b1a95c68a092fba078fd1/`,这里却没有 `/`。

**【解决方案】**

更新 path：

```yaml
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: vscode-01
            port:
              number: 8888
        path: /stable-487e0b6eb726a84faf6b1a95c68a092fba078fd1(/?|$)
        pathType: ImplementationSpecific
```

此时 ws 流量应该也可以被正常匹配到，然后 rewrite 到 `/` 去了。

## 最后效果

且看：

![](9.png)

不总结了，着急下班。

（好像还得解决多实例的访问问题，现在的 URL 是同一个，固定的。不过，不急，再说吧，着急下班。）

