NET Core 2.0 微服务跨平台实践
相关博文:
阅读目录:
- Docker 运行 Consul 环境
- Docker 运行 Fabio 环境
- 使用 Consul 注册 ASP.NET Core 2.0 服务
- 使用 Docker 发布部署 ASP.NET Core 2.0 服务
本篇博文的目的:在 Mac OS 中使用 VS Code 开发 ASP.NET Core 2.0 应用程序,然后在 Ubuntu 服务器配置 Docker 环境,并使用 Docker 运行 Consul 和 Fabio 环境,最后使用 Docker 运行 ASP.NET Core 2.0 应用程序。
你要的项目源码:https://github.com/yuezhongxin/HelloDocker.Sample
上面配置看起来还蛮简单,但实际去操作的时候,还是遇到了蛮多的问题,并且花了很多的时间去解决,比如 Docker 运行 Consul 和 Fabio,下面详细说下过程。
1. Docker 运行 Consul 环境
关于 Consul 的概念:
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,比如 Airbnb 的 SmartStack 等相比,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 用 Golang 实现,因此具有天然可移植性(支持 Linux、windows 和 Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。
Consul Docker 镜像地址:https://hub.docker.com/_/consul/
配置 Consul 的微服务集群环境,需要先配置下 Server 服务端(需要独立服务器环境),配置命令(没有使用 Docker):
$ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -ui-dir=./dist -config-dir /etc/consul.d -bind=10.9.10.110
一开始,我想在 Mac OS 环境中使用 Docker 配置 Consul Client 客户端,但遇到了一些问题,配置命令:
$ docker run -d --net=host --name=consul-client consul agent -bind=10.9.10.190 -client=0.0.0.0 -node=mac-xishuai -retry-join=10.9.10.236
先解析下命令的意思:
--net=host
:host
网络模式,容器的网络接口和主机一样,也就是共享一个 IP 地址,如果没有此命令,默认是bridge
网络模式,也就是我们常用的桥接模式,Docker 会分配给容器一个独立的 IP 地址(端口也是独立的),并且容器和主机之间可以相互访问。-bind=
:Consul Client 绑定的 IP 地址,一般是内网的私有 IP 地址,需要内网服务器之前可以相互访问到,注意并不是127.0.0.1
。-retry-join=
:加入 Consul 集群中,地址是 Consul Server 的 IP 地址,也可以是-join=
,加上retry
会不断进行重试。
一台服务器一般会配置一个 Consul Client,所以我们可以直接让 Consul 容器和主机的 IP 地址一样(我使用的),但使用了 Docker 之后,一台服务器就可以配置多个 Consul Client,我们就可以使用bridge
网络模式,一台服务器可以完成配置整个 Consul 集群环境。
这里需要再重点说下-client=
,一开始我没有理解,先看下官方说明:
If you want to expose the Consul interfaces to other containers via a different network, such as the bridge network, use the
-client
option for Consul.
With this configuration, Consul's client interfaces will be bound to the bridge IP and available to other containers on that network, but not on the host network. Note that we still keep the cluster address out on the host network for performance. Consul will also accept the-client=0.0.0.0
option to bind to all interfaces.
啥意思呢?Consul 服务注册的时候,一般是通过 HTTP API 接口进行注册,比如:http://10.9.10.190:8500/v1/agent/service/register,就是往 Consul 集群中注册服务,需要注意的是,10.9.10.190
一般是 Consul Client 的 IP 地址(也可以是 Consul Server),-client
配置的就是此地址,简单来说,就是用来服务注册并能访问到的地址,换句话说,服务注册可以跨服务器(服务和 Consul Client 并不需要在同一台服务器上),0.0.0.0
表示任何本机的相关 IP 地址都可以访问,推荐此配置。
这里需要再说明下,Docker 部署 ASP.NET Core 2.0、Consul 和 Fabio 有两种方式:
- 使用一个 Docker 容器:很简单,在一个容器中完成服务部署,并且配置 Consul 和 Fabio 环境,这样容器就会很臃肿,并且每次发布的时候都得重新配置 Consul 和 Fabio 环境,如果服务很多的话,想想就觉得恐怖。
- 分别独立 Docker 容器:服务部署、配置 Consul 和 Fabio 环境,都是独立容器实现,互不影响,也可以跨服务实现,简单灵活。
显而易见,推荐第二种方式。
回到正题,上面配置命令,在 Mac OS 报如下错误:
$ docker logs consul-client ==> Starting Consul agent... ==> Error starting agent: Failed to start Consul client: Failed to start lan serf: Failed to create memberlist: Could not set up network transport: failed to obtain an address: Failed to start TCP listener on "10.9.10.190" port 8301: listen tcp 10.9.10.190:8301: bind: cannot assign requested address
这个问题花了很多时间也没有解决,奇怪的是不使用 Docker,直接运行 Consul Client 配置命令,却是可以的,后来没办法,我就在 Mac OS 中使用 Ubuntu 虚拟机了(版本 14.04),使用的 Vagrant 管理工具。
再重新运行配置命令:
$ docker run -d --net=host --name=consul-client consul agent -bind=10.9.10.89 -client=0.0.0.0 -node=vagrant-ubuntu-xishuai -retry-join=10.9.2.236 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9c4988cf475f consul "docker-entrypoint..." 2 seconds ago Up 2 seconds consul-client $ docker logs consul-client ==> Starting Consul agent... ==> Consul agent running! Version: 'v1.0.0' Node ID: '34e63f0a-d361-f152-3803-b9fda0642e4d' Node name: 'vagrant-ubuntu-xishuai' Datacenter: 'dc1' (Segment: '') Server: false (Bootstrap: false) Client Addr: [0.0.0.0] (HTTP: 8500, HTTPS: -1, DNS: 8600) Cluster Addr: 10.9.10.89 (LAN: 8301, WAN: 8302) Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false ==> Log data will now stream in as it occurs: 2017/11/14 06:40:52 [INFO] serf: EventMemberJoin: vagrant-ubuntu-xishuai 10.9.10.89 2017/11/14 06:40:52 [INFO] agent: Started DNS server 0.0.0.0:8600 (udp) 2017/11/14 06:40:52 [INFO] agent: Started DNS server 0.0.0.0:8600 (tcp) 2017/11/14 06:40:52 [INFO] agent: Started HTTP server on [::]:8500 (tcp) 2017/11/14 06:40:52 [INFO] agent: Retry join LAN is supported for: aws azure gce softlayer 2017/11/14 06:40:52 [INFO] agent: Joining LAN cluster... 2017/11/14 06:40:52 [INFO] agent: (LAN) joining: [10.9.2.236] 2017/11/14 06:40:52 [WARN] manager: No servers available 2017/11/14 06:40:52 [ERR] agent: failed to sync remote state: No known Consul servers 2017/11/14 06:40:52 [INFO] serf: EventMemberJoin: agent_1 10.9.2.236 2017/11/14 06:40:52 [INFO] agent: (LAN) joined: 1 Err: <nil> 2017/11/14 06:40:52 [INFO] agent: Join LAN completed. Synced with 1 initial agents 2017/11/14 06:40:52 [INFO] consul: adding server agent_1 (Addr: tcp/10.9.2.236:8300) (DC: dc1)
打开 Consul UI 界面,就可以看到我们配置的 Consul Client 了:
2. Docker 运行 Fabio 环境
Fabio 是一个快速、现代、zero-conf 负载均衡 HTTP(S) 路由器,用于部署 Consul 管理的微服务。
Fabio Docker 镜像地址:https://hub.docker.com/r/magiconair/fabio/
配置命令:
$ docker run -d --net=host --name=fabio -e 'registry_consul_addr=10.9.10.89:8500' magiconair/fabio
执行日志:
$ docker logs fabio 2017/11/14 09:43:49 [INFO] Setting log level to INFO 2017/11/14 09:43:49 [INFO] Runtime config { "Proxy": { "Strategy": "rnd", "Matcher": "prefix", "NoRouteStatus": 404, "MaxConn": 10000, "ShutdownWait": 0, "DialTimeout": 30000000000, "ResponseHeaderTimeout": 0, "KeepAliveTimeout": 0, "FlushInterval": 1000000000, "LocalIP": "10.0.2.15", "ClientIPHeader": "", "TLSHeader": "", "TLSHeaderValue": "", "GZIPContentTypes": null, "RequestID": "" }, "Registry": { "Backend": "consul", "Static": { "Routes": "" }, "File": { "Path": "" }, "Consul": { "Addr": "10.9.10.89:8500", "Scheme": "http", "Token": "", "KVPath": "/fabio/config", "TagPrefix": "urlprefix-", "Register": true, "ServiceAddr": ":9998", "ServiceName": "fabio", "ServiceTags": null, "ServiceStatus": [ "passing" ], "CheckInterval": 1000000000, "CheckTimeout": 3000000000, "CheckScheme": "http", "CheckTLSSkipVerify": false }, "Timeout": 10000000000, "Retry": 500000000 }, "Listen": [ { "Addr": ":9999", "Proto": "http", "ReadTimeout": 0, "WriteTimeout": 0, "CertSource": { "Name": "", "Type": "", "CertPath": "", "KeyPath": "", "ClientCAPath": "", "CAUpgradeCN": "", "Refresh": 0, "Header": null }, "StrictMatch": false, "TLSMinVersion": 0, "TLSMaxVersion": 0, "TLSCiphers": null } ], "Log": { "AccessFormat": "common", "AccessTarget": "", "RoutesFormat": "delta", "Level": "INFO" }, "Metrics": { "Target": "", "Prefix": "{{clean .Hostname}}.{{clean .Exec}}", "Names": "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", "Interval": 30000000000, "Timeout": 10000000000, "Retry": 500000000, "GraphiteAddr": "", "StatsDAddr": "", "Circonus": { "APIKey":