问一下大家,我收到同一公司两个业务团队offer,一个是做容器,一个是API网关,这两个哪个的业务前景更好呀?
答:容器

容器

总所周知 Docker 和 Kubernetes 等的云计算项目都是用 Go 语言写的。docker 的技术出现,的确为实施人员解决了让他们头痛的问题。 最近新项目也想用 docker 将开发环境部署到开发人员机器上,从而了节省开发人员搭建繁琐的开发环境的时间。因此才想花些业余时间,学习学习 docker。在学习过程中,也感受到 docker 的 magic。今天就给大家简单地介绍一下。
由于 Docker 直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。
同时对 docker 是怎么写出来的产出兴趣,由此搜集一些资料来学习 docker 是如何实现的。想尝试自己写一个简单容器。
当我们运行 docker 时
docker:docker run <容器> 命令 参数(docker run hello-world)
实现:go run main.go run 命令 参数
一个是做容器,一个是API网关 - 图1
创建一个 go 项目,创建 main.go 文件。
引入所需要包,有关 os 包的具体使用方法,我会随后在我公众号中,推送给大家分享。
一个是做容器,一个是API网关 - 图2
在 main 入口方法中,根据运行时输入第一个参数的值来判断是否运行 run 方法。
一个是做容器,一个是API网关 - 图3
在 run 方法中,首先从第二个参数打印到最后参数为止的所有参数。
接下来执行命名,有关 exec 的使用方法,下一次通过具体示例为大家介绍。
must 方法对异常进行处理。
一个是做容器,一个是API网关 - 图4
我们现在就可以运行一下程序,在终端输入下图中的命令
一个是做容器,一个是API网关 - 图5
一个是做容器,一个是API网关 - 图6
哈哈 我们到此为止已经迈出了第一步。
一个是做容器,一个是API网关 - 图7
接下来我们再尝试运行 go run main.go run /bin/bash
一个是做容器,一个是API网关 - 图8
一个是做容器,一个是API网关 - 图9
然后进入命令行,可以输入一些命令,最后通过 exit 退出。
但是现在我们进入的容器还不是隔离的,如下图中,当进入后我们查看 hostname,然后 hostname 修改用户名后退出(exit)容器,我们再次用 hostname 命令查看用户名,发现这时系统的 hostname 也发随着发生改变。这说明我们没有隔离容器与系统。
一个是做容器,一个是API网关 - 图10
为了解决这个问题,我们需要调整一下代码。
一个是做容器,一个是API网关 - 图11
引入 syscall 这个可以调用底层的包,有关 syscall 的具体的用法,随后为大家分享。
一个是做容器,一个是API网关 - 图12
然后用syscall 将 UTS 复制一份以达到隔离目的
一个是做容器,一个是API网关 - 图13
再次运行程序,查看结果大家可能已经发现了这次在容器中修改 hostname 不会再影响到系统的 hostname 了
一个是做容器,一个是API网关 - 图14

API网关

在最近的一个项目中,采用了微服务架构-go-kit进行后端的开发。在微服务架构风格中,一个大应用被拆分成为了多个小的服务系统提供出来,这些小的系统他们可以自成体系,也就是说这些小系统可以拥有自己的数据库,框架甚至语言等,因此我们需要设计一个API 网关(API Gataway),其实网上已经有较多现成的实现框架,但是本项目的需求是比较简单的,因此将使用Golang自行实现。

实现
API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。

用于实现API网关的技术有很多,大致分为这么几类:

通用反向代理:Nginx、Haproxy、……
网络编程框架:Netty、Servlet、……
API网关框架:Spring Cloud Gateway、Zuul、Zuul2、……
API网关最基本的功能就是反向代理。其实现方式有很多,本文将基于标准库net/http/httputil包中的ReverseProxy类型来实现实现一个简单的反向代理。反向代理的实现主要涉及到func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy和type

  1. func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy
  1. // NewSingleHostReverseProxy returns a new ReverseProxy that routes
  2. // URLs to the scheme, host, and base path provided in target. If the
  3. // target's path is "/base" and the incoming request was for "/dir",
  4. // the target request will be for /base/dir.
  5. // NewSingleHostReverseProxy does not rewrite the Host header.
  6. // To rewrite Host headers, use ReverseProxy directly with a custom
  7. // Director policy.
  8. func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
  9. targetQuery := target.RawQuery
  10. director := func(req *http.Request) {
  11. req.URL.Scheme = target.Scheme
  12. req.URL.Host = target.Host
  13. req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
  14. if targetQuery == "" || req.URL.RawQuery == "" {
  15. req.URL.RawQuery = targetQuery + req.URL.RawQuery
  16. } else {
  17. req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
  18. }
  19. if _, ok := req.Header["User-Agent"]; !ok {
  20. // explicitly disable User-Agent so it's not set to default value
  21. req.Header.Set("User-Agent", "")
  22. }
  23. }
  24. return &ReverseProxy{Director: director}
  25. }

NewSingleHostReverseProxy返回一个新的ReverseProxy,将URLs请求路由到targe的指定的scheme, host, base path。

  1. // ReverseProxy is an HTTP Handler that takes an incoming request and
  2. // sends it to another server, proxying the response back to the
  3. // client.
  4. type ReverseProxy struct {
  5. // Director must be a function which modifies
  6. // the request into a new request to be sent
  7. // using Transport. Its response is then copied
  8. // back to the original client unmodified.
  9. // Director must not access the provided Request
  10. // after returning.
  11. Director func(*http.Request)
  12. Transport http.RoundTripper
  13. FlushInterval time.Duration
  14. ErrorLog *log.Logger
  15. BufferPool BufferPool
  16. // ModifyResponse is an optional function that modifies the
  17. // Response from the backend. It is called if the backend
  18. // returns a response at all, with any HTTP status code.
  19. // If the backend is unreachable, the optional ErrorHandler is
  20. // called without any call to ModifyResponse.
  21. //
  22. // If ModifyResponse returns an error, ErrorHandler is called
  23. // with its error value. If ErrorHandler is nil, its default
  24. // implementation is used.
  25. ModifyResponse func(*http.Response) error
  26. ErrorHandler func(http.ResponseWriter, *http.Request, error)
  27. }

ReverseProxy类型有两个重要的属性,分别是Director和ModifyResponse,这两个属性都是函数类型,在接收到客户端请求时,ServeHTTP函数首先调用Director函数对接受到的请求体进行修改,例如修改请求的目标地址、请求头等;然后使用修改后的请求体发起新的请求,接收到响应后,调用ModifyResponse函数对响应进行修改,最后将修改后的响应体拷贝并响应给客户端,这样就实现了反向代理的整个流程。

NewSingleHostReverseProxy中源码已经对传入的URLs进行解析并且完成了Director的修改,我们只需要调用NewSingleHostReverseProxy函数并且传入目标服务器的URL即可,一个简单的反向代理就完成了啦。

代码

实例代码只涉及微服务中 user 与 auth模块,可以根据实际需求自行修改部分

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. "net/http/httputil"
  7. "net/url"
  8. "strings"
  9. )
  10. type handle struct {
  11. host string
  12. port string
  13. }
  14. type Service struct {
  15. auth *handle
  16. user *handle
  17. }
  18. func (this *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  19. var remote *url.URL
  20. if strings.Contains(r.RequestURI, "api/auth") {
  21. remote, _ = url.Parse("http://" + this.auth.host + ":" + this.auth.port)
  22. } else if strings.Contains(r.RequestURI, "api/user") {
  23. remote, _ = url.Parse("http://" + this.user.host + ":" + this.user.port)
  24. } else {
  25. fmt.Fprintf(w, "404 Not Found")
  26. return
  27. }
  28. proxy := httputil.NewSingleHostReverseProxy(remote)
  29. proxy.ServeHTTP(w, r)
  30. }
  31. func startServer() {
  32. // 注册被代理的服务器 (host, port)
  33. service := &Service{
  34. auth: &handle{host: "127.0.0.1", port: "8081"},
  35. user: &handle{host: "127.0.0.1", port: "8082"},
  36. }
  37. err := http.ListenAndServe(":8888", service)
  38. if err != nil {
  39. log.Fatalln("ListenAndServe: ", err)
  40. }
  41. }
  42. func main() {
  43. startServer()
  44. }

API网关在微服务系统中是必要的组件,承担了负载均衡,反向代理,统计,鉴权,监控等等职责。此处只就反向代理的功能做一个实现。

主要用到httputil.ReverseProxy对象。它的Director表示要重定向到的操作,ModifyResponse是你可以操作返回的结果。
proxy := httputil.NewSingleHostReverseProxy(target)
查看源码会发现NewSingleHostReverseProxy() 的实现中,对req.URL.Path的包装不太符合实际需求,于是需要自己来实现Director。另外此方法返回的ReverseProxy对象并没有实现ModifyResponse

一个简单的反向代理服务器:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "log"
  7. "net/http"
  8. "net/http/httputil"
  9. "net/url"
  10. "strings"
  11. )
  12. type handle struct {
  13. host string
  14. port string
  15. }
  16. type Service struct {
  17. auth *handle
  18. user *handle
  19. }
  20. func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  21. var target *url.URL
  22. if strings.Contains(r.RequestURI, "/api/stats") {
  23. target, _ = url.Parse("http://" + s.auth.host + ":" + s.auth.port + "/show/stats")
  24. } else if strings.Contains(r.RequestURI, "/api/autoCommentList") {
  25. target, _ = url.Parse("http://" + s.user.host + ":" + s.user.port + "/show/autoCommentList")
  26. } else {
  27. fmt.Fprintf(w, "404 Not Found")
  28. return
  29. }
  30. targetQuery := target.RawQuery
  31. director := func(req *http.Request) {
  32. req.URL.Scheme = target.Scheme
  33. req.URL.Host = target.Host
  34. req.URL.Path = target.Path
  35. if targetQuery == "" || req.URL.RawQuery == "" {
  36. req.URL.RawQuery = targetQuery + req.URL.RawQuery
  37. } else {
  38. req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
  39. }
  40. if _, ok := req.Header["User-Agent"]; !ok {
  41. req.Header.Set("User-Agent", "")
  42. }
  43. }
  44. proxy := &httputil.ReverseProxy{Director: director}
  45. proxy.ModifyResponse = func(response *http.Response) error {
  46. cont, _ := ioutil.ReadAll(response.Body)
  47. fmt.Println(string(cont))
  48. response.Body = ioutil.NopCloser(bytes.NewReader(cont))
  49. return nil
  50. }
  51. proxy.ServeHTTP(w, r)
  52. }
  53. func startServer() {
  54. service := &Service{
  55. auth: &handle{host: "192.168.2.157", port: "8007"},
  56. user: &handle{host: "192.168.2.157", port: "8007"},
  57. }
  58. err := http.ListenAndServe(":8888", service)
  59. if err != nil {
  60. log.Fatalln("ListenAndServe: ", err)
  61. }
  62. }
  63. func main() {
  64. startServer()
  65. }