手工打造像 Istio 中一样的 Sidecar 代理

Posted ServiceMesher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手工打造像 Istio 中一样的 Sidecar 代理相关的知识,希望对你有一定的参考价值。

译者:邱世达

审阅:孙海洲

原文:https://venilnoronha.io/hand-crafting-a-sidecar-proxy-like-istio


sidecar代理模式是一个重要的概念,它允许 Istio 为服务网格中运行的服务提供路由、度量、安全和其他功能。


在这篇文章中,我将解释为Istio提供支持的关键技术,同时还将向您展示一种构建简单的HTTP流量嗅探sidecar代理的方法。


引言

服务网格的实现通常依赖于sidecar代理,这些代理使得服务网格能够控制、观察和加密保护应用程序。sidecar代理是反向代理,所有流量在到达目标服务之前流过它。代理将分析流经自己的流量并生成有用的统计信息,而且还能提供灵活的路由功能。此外,代理还可以使用mTLS来加密保护应用程序流量。

手工打造像 Istio 中一样的 Sidecar 代理

在这篇文章中,我们将构建一个简单的sidecar代理,它可以嗅探HTTP流量并生成统计信息,例如请求大小,响应状态等。然后,我们将在Kubernetes Pod中部署HTTP服务,配置sidecar代理,并检查生成的统计信息。

构建HTTP流量嗅探代理

Istio依靠Envoy来代理网络流量。Envoy代理被打包为一个容器,并部署在一个Pod中的服务容器旁边。在这篇文章中,我们将使用Golang来构建一个可以嗅探HTTP流量的微型代理。

 
   
   
 
  1. const (

  2. proxyPort = 8000

  3. servicePort = 80

  4. )

现在,我们可以开始编写代理的骨架代码了。代理将侦听 proxyPort上的请求并将请求转发给 servicePort。代理将在服务每个请求后最终打印统计信息。

 
   
   
 
  1. // Create a structure to define the proxy functionality.

  2. type Proxy struct{}


  3. func (p *Proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {

  4. // Forward the HTTP request to the destination service.

  5. res, duration, err := p.forwardRequest(req)


  6. // Notify the client if there was an error while forwarding the request.

  7. if err != nil {

  8. http.Error(w, err.Error(), http.StatusBadGateway)

  9. return

  10. }


  11. // If the request was forwarded successfully, write the response back to

  12. // the client.

  13. p.writeResponse(w, res)


  14. // Print request and response statistics.

  15. p.printStats(req, res, duration)

  16. }


  17. func main() {

  18. // Listen on the predefined proxy port.

  19. http.ListenAndServe(fmt.Sprintf(":%d", proxyPort), &Proxy{})

  20. }

代理最重要的部分是它转发请求的能力。我们首先在代理实现中定义此功能。

 
   
   
 
  1. func (p *Proxy) forwardRequest(req *http.Request) (*http.Response, time.Duration, error) {

  2. // Prepare the destination endpoint to forward the request to.

  3. proxyUrl := fmt.Sprintf("http://127.0.0.1:%d%s", servicePort, req.RequestURI)


  4. // Print the original URL and the proxied request URL.

  5. fmt.Printf("Original URL: http://%s:%d%s ", req.Host, servicePort, req.RequestURI)

  6. fmt.Printf("Proxy URL: %s ", proxyUrl)


  7. // Create an HTTP client and a proxy request based on the original request.

  8. httpClient := http.Client{}

  9. proxyReq, err := http.NewRequest(req.Method, proxyUrl, req.Body)


  10. // Capture the duration while making a request to the destination service.

  11. start := time.Now()

  12. res, err := httpClient.Do(proxyReq)

  13. duration := time.Since(start)


  14. // Return the response, the request duration, and the error.

  15. return res, duration, err

  16. }

现在我们得到了代理请求的响应,让我们定义将其写回客户端的逻辑。

 
   
   
 
  1. func (p *Proxy) writeResponse(w http.ResponseWriter, res *http.Response) {

  2. // Copy all the header values from the response.

  3. for name, values := range res.Header {

  4. w.Header()[name] = values

  5. }


  6. // Set a special header to notify that the proxy actually serviced the request.

  7. w.Header().Set("Server", "amazing-proxy")


  8. // Set the status code returned by the destination service.

  9. w.WriteHeader(res.StatusCode)


  10. // Copy the contents from the response body.

  11. io.Copy(w, res.Body)


  12. // Finish the request.

  13. res.Body.Close()

  14. }

代理的最后一部分是打印统计信息。让我们继续将其实现。

 
   
   
 
  1. func (p *Proxy) printStats(req *http.Request, res *http.Response, duration time.Duration) {

  2. fmt.Printf("Request Duration: %v ", duration)

  3. fmt.Printf("Request Size: %d ", req.ContentLength)

  4. fmt.Printf("Response Size: %d ", res.ContentLength)

  5. fmt.Printf("Response Status: %d ", res.StatusCode)

  6. }

至此,我们已经构建了一个功能齐全的HTTP流量嗅探代理。

为代理构建容器镜像

Istio打包了Envoy并将其作为sidecar容器运行在服务容器旁边。让我们构建一个代理容器镜像,运行上面的Go代码来模仿Istio的运行模式。

 
   
   
 
  1. # Use the Go v1.12 image for the base.

  2. FROM golang:1.12


  3. # Copy the proxy code to the container.

  4. COPY main.go .


  5. # Run the proxy on container startup.

  6. ENTRYPOINT [ "go" ]

  7. CMD [ "run", "main.go" ]


  8. # Expose the proxy port.

  9. EXPOSE 8000

要构建代理容器镜像,我们可以简单地执行以下Docker命令:

 
   
   
 
  1. $ docker build -t venilnoronha/amazing-proxy:latest -f Dockerfile .

设置Pod网络

Kubernetes 网络

为了更好地理解,让我们列出Kubernetes向Pod公开的网络接口。

 
   
   
 
  1. $ kubectl run -i --rm --restart=Never busybox --image=busybox -- sh -c "ip addr"


  2. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000

  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

  4. inet 127.0.0.1/8 scope host lo

  5. valid_lft forever preferred_lft forever


  6. 174: eth0@if175: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue

  7. link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff

  8. inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0

  9. valid_lft forever preferred_lft forever

使用iptables进行端口映射

iptables最简单的用途是将一个端口映射到另一个端口。我们可以利用它来透明地将流量路由到我们的代理。Istio正是基于这个确切的概念来建立它的Pod网络。

这里的想法是将 eth0接口上的服务端口( 80)映射到代理端口( 8000)。这将确保每当容器尝试通过端口 80访问服务时,来自容器外部的流量就会路由到代理。如上图所示,我们让 lo接口将Pod内部流量直接路由到目标服务,即没有跳转到代理服务。

Init容器

Kubernetes允许在Pod运行普通容器之前运行 init容器。Istio使用init容器来设置Pod网络,以便设置必要的iptables规则。这里,让我们做同样的事情来将Pod外部流量路由到代理。

 
   
   
 
  1. #!/bin/bash


  2. # Forward TCP traffic on port 80 to port 8000 on the eth0 interface.

  3. iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 80 -j REDIRECT --to-port 8000


  4. # List all iptables rules.

  5. iptables -t nat --list

我们现在可以使用此初始化脚本创建Docker容器镜像。

 
   
   
 
  1. # Use the latest Ubuntu image for the base.

  2. FROM ubuntu:latest


  3. # Install the iptables command.

  4. RUN apt-get update &&

  5. apt-get install -y iptables


  6. # Copy the initialization script into the container.

  7. COPY init.sh /usr/local/bin/


  8. # Mark the initialization script as executable.

  9. RUN chmod +x /usr/local/bin/init.sh


  10. # Start the initialization script on container startup.

  11. ENTRYPOINT ["init.sh"]

要构建Docker镜像,只需执行以下命令:

 
   
   
 
  1. $ docker build -t venilnoronha/init-networking:latest -f Dockerfile .

演示

我们已经构建了一个代理和一个init容器来建立Pod网络。现在是时候进行测试了。为此,我们将使用httpbin容器作为服务。

部署 Deployment

Istio自动注入init容器和代理。但是,对于我们的实验,可以手动制作Pod yaml。

 
   
   
 
  1. apiVersion: v1

  2. kind: Pod

  3. metadata:

  4. name: httpbin-pod

  5. labels:

  6. app: httpbin

  7. spec:

  8. initContainers:

  9. - name: init-networking

  10. image: venilnoronha/init-networking

  11. securityContext:

  12. capabilities:

  13. add:

  14. - NET_ADMIN

  15. privileged: true

  16. containers:

  17. - name: service

  18. image: kennethreitz/httpbin

  19. ports:

  20. - containerPort: 80

  21. - name: proxy

  22. image: venilnoronha/amazing-proxy

  23. ports:

  24. - containerPort: 8000

我们已经设置了具有 root权限的init容器,并将 proxyservice配置为普通容器。要在Kubernetes集群上部署它,我们可以执行以下命令:

 
   
   
 
  1. $ kubectl apply -f httpbin.yaml

测试

为了测试部署,我们首先确定Pod的ClusterIP。为此,我们可以执行以下命令:

 
   
   
 
  1. $ kubectl get pods -o wide

  2. NAME READY STATUS RESTARTS AGE IP NODE

  3. httpbin-pod 2/2 Running 0 21h 172.17.0.4 minikube

我们现在需要从Pod外部生成流量。为此,我将使用 busybox容器通过 curl发出HTTP请求。

首先,我们向httpbin服务发送一个GET请求。

 
   
   
 
  1. $ kubectl run -i --rm --restart=Never busybox --image=odise/busybox-curl

  2. -- sh -c "curl -i 172.17.0.4:80/get?query=param"


  3. HTTP/1.1 200 OK

  4. Content-Length: 237

  5. Content-Type: application/json

  6. Server: amazing-proxy

然后,再发送一个POST请求。

 
   
   
 
  1. $ kubectl run -i --rm --restart=Never busybox --image=odise/busybox-curl

  2. -- sh -c "curl -i -X POST -d 'body=parameters' 172.17.0.4:80/post"


  3. HTTP/1.1 200 OK

  4. Content-Length: 317

  5. Content-Type: application/json

  6. Server: amazing-proxy

最后,向 /status端点发送一个GET请求。

 
   
   
 
  1. $ kubectl run -i --rm --restart=Never busybox --image=odise/busybox-curl

  2. -- sh -c "curl -i http://172.17.0.4:80/status/429"


  3. HTTP/1.1 429 Too Many Requests

  4. Content-Length: 0

  5. Content-Type: text/html; charset=utf-8

  6. Server: amazing-proxy

请注意,我们将请求发送到端口 80,即服务端口而不是代理端口。 iptables规则确保首先将其路由到代理,然后将请求转发给服务。此外,我们还看到了额外的请求头 Server:amazing-proxy,这个请求头是我们手动实现的的代理自动加上的。

代理统计

现在我们来看看代理生成的统计数据。为此,我们可以运行以下命令:

 
   
   
 
  1. $ kubectl logs httpbin-pod --container="proxy"


  2. Original URL: http://172.17.0.4:80/get?query=param

  3. Proxy URL: http://127.0.0.1:80/get?query=param

  4. Request Duration: 1.979348ms

  5. Request Size: 0

  6. Response Size: 237

  7. Response Status: 200


  8. Original URL: http://172.17.0.4:80/post

  9. Proxy URL: http://127.0.0.1:80/post

  10. Request Duration: 2.026861ms

  11. Request Size: 15

  12. Response Size: 317

  13. Response Status: 200


  14. Original URL: http://172.17.0.4:80/status/429

  15. Proxy URL: http://127.0.0.1:80/status/429

  16. Request Duration: 1.191793ms

  17. Request Size: 0

  18. Response Size: 0

  19. Response Status: 429

如您所见,我们确实看到代理的结果与我们生成的请求相匹配。

结论

本文中,我们实现了一个简单的HTTP流量嗅探代理,使用init容器将其嵌入Kubernetes Pod与原有服务无缝连接。而且,我们也了解了 iptables是如何提供灵活的网络,以便在处理代理时提供优良的用户体验的。最重要的是,我们已经学会了关于Istio实现的一些关键概念。

1

END

1



Service Mesh Weekly #34


2019.03.11 - 2019.03.17

博客

1. Istio 庖丁解牛一:组件概览,http://www.servicemesher.com/blog/istio-analysis-1/ ,by 钟华

2. Knative 入门系列1:knative 概述,http://www.servicemesher.com/blog/knative-overview/ ,by Brian McClain & Bryan Friedman,陈家栋 译,宋净超、孙海洲、徐鹏、邱世达 审校

3. Knative 入门系列2:serving 介绍 http://www.servicemesher.com/blog/knative-serving/ ,by Brian Mchttp://www.servicemesher.com/blog/hand-crafting-a-sidecar-proxy-like-istio/Clain & Bryan Friedman,杨铁党 译,孙海洲、邱世达、宋净超、徐鹏 审校

4. ServiceMesher社区推出合著Istio Handbook,https://juejin.im/post/5c88d1016fb9a049c43e87a8 ,by ServiceMesher 社区

5. Knative 入门系列3:Build 介绍,http://www.servicemesher.com/getting-started-with-knative/build.html , 孙海洲 译,邱世达、陈冬、杨铁党、宋净超 审校


活动

1. 第一届 Service Mesh Day,3月29日,旧金山,http://servicemeshday.com/

2. 第一届 SOFA Meetup,3月24日,北京,https://tech.antfin.com/activities/382


新闻

1. F5 收购 nginxhttps://www.nginx.com/blog/nginx-joins-f5/

2. 基于Envoy和Istio的企业级服务网格解决方案初创公司 tetrate.io 获得1250万美元融资,https://www.tetrate.io/blog/press-release

3. 开创 Service Mesh 概念并开源 Linkerd 的服务网格初创公司 Buoyant 获得 Google Ventures 1000万美元投资,https://buoyant.io/2019/03/14/welcome-to-the-linkerd-party-google-ventures/

本文归档到https://github.com/servicemesher/trans

 推荐阅读 




以上是关于手工打造像 Istio 中一样的 Sidecar 代理的主要内容,如果未能解决你的问题,请参考以下文章

Istio Sidecar 注入过程解密

示例说明 | 想要正确使用Istio?这份sidecar说明书必不可少

Istio1.12.1 Sidecar注入配置

Istio实战(十五)-Sidecar注入

Istio实战-代理规则配置:Sidecar

Istio实战(十六)-Sidecar流量拦截