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":