.net6使用nacos 集群部署,负载均衡调用 。docker swarm 集群部署.net6项目 电脑版发表于:2022/12/27 15:45 tn2>我们这里的k8s测试环境暂时用不了了,这里先使用docker swarm来进行一下集群部署。 tn3>.net6使用nacos实现服务注册与服务发现: https://www.tnblog.net/aojiancc2/article/details/7931 .net6使用nacos作为配置中心: https://user.tnblog.net/UpdateArticle/UpdateArticle/7870 docker安装nacos v2.1.2: https://www.tnblog.net/aojiancc2/article/details/7865 [TOC] ## Docker Swarm环境 ![](https://img.tnblog.net/arcimg/aojiancc2/b98baab8c47743dc8d47c77c65eab449.png) 就两个电脑的一个简单集群,一个主节点,一个子节点 ## 上传发布的项目,打包镜像 上传发布后的项目文件与dockerfile,然后使用命令打包镜像 ``` docker build -t nacoslearn . ``` ![](https://img.tnblog.net/arcimg/aojiancc2/982b645901524c30b47cfd97d9b1d10b.png) 打包的时候可以在后面接版本,比如接一个v1版本: ``` docker build -t nacoslearn:v1 . ``` ## 创建一个Overlay模式的自定义网络。方便集群容器中的网络管理与容器互通 ``` docker network create -d overlay nacoslearn_net ``` ![](https://img.tnblog.net/arcimg/aojiancc2/ed43ff69f7c0419a8e1ac534d346609f.jpg) 创建后可以使用docker network ls查看,如上图所示。 也可以使用docker network ls | grep nacoslearn_net查看,这样就只会查看到nacoslearn_net网络了 ![](https://img.tnblog.net/arcimg/aojiancc2/09be32609a5746c9bd9febd7370b5589.png) Overlay网络模式介绍:用于跨主机网络,集群访问,应用层通信的网络。 ## 使用创建的自定义网络部署.net6项目 刚刚镜像已经打好了,可以直接跑服务了 ``` docker service create --replicas 5 --network nacoslearn_net --name nacoslearn -p 8805:8805 nacoslearn ``` ![](https://img.tnblog.net/arcimg/aojiancc2/d9f1296e6161467c8054faf51d8c086d.png) tn2>上面创建了一个具有5个副本(--replicas 5 )的nacoslearn服务,nacoslearn,使用网络nacoslearn_net tn4>如果镜像我们是上传到镜像库了的,就可以不需要提前在节点上创建nacoslearn镜像,这个命令执行后会自动下载对应的容器镜像 **可以使用下面的命令查看网络的使用详情:** ``` docker network inspect nacoslearn_net ``` ![](https://img.tnblog.net/arcimg/aojiancc2/fae346d0be774f29ba9b2fc37ae8592c.jpg) ## 使用 docker service ls 查看正在运行服务的列表 ![](https://img.tnblog.net/arcimg/aojiancc2/59e81c278c644a5895417f4ed9f0b3b0.png) 可以看到,刚刚我们创建的5个副本是正常启动起来的 ## 查看服务被运行到什么节点 ``` docker service ps nacoslearn ``` ![](https://img.tnblog.net/arcimg/aojiancc2/bad0c3e75a484d5f82d649ccad3e325b.jpg) tn2>这里我们可以看到5个副本都跑到了主节点上,因为在node节点没有找到这个镜像去互联网下载又没有成功,所以重试几次后都失败了,就全部运行到主节点了。 测试的时候可以把镜像打包上传到镜像库或者是在子节点在打包一个镜像就可以在子节点跑了。 ## 服务成功跑起来后就可以在nacos查看服务的情况 ![](https://img.tnblog.net/arcimg/aojiancc2/5ec458c0b0fb45918ad07f100fe5e35b.jpg) **可以看到一共有5个实例,点击进去查看详情** ![](https://img.tnblog.net/arcimg/aojiancc2/4792d4589de74c449a1ae7675d0dd121.jpg) tn6>可以看到5个节点的ip地址等信息,这里要注意这里获取的ip地址格式10.0.0.x和我们查看自定义网络详情的10.0.2.x不一致,是因为nacos获取ip的时候默认是获取第一张网卡的,我们这里容器内部的网卡其实有多张,我们可以去nacos配置以下,获取我们想要的ip。虽然这里的ip也不会影响我们通过docker swarm来进行集群的负债均衡调用,但是我们想要通过nacos服务发现后进去内部调用就要注意了,获取的网卡要对。 ## 动态更新镜像 比如我们修改了一块功能,想要发布新的功能就可以使用动态更新镜像。 **先打包一个新的镜像,这里我们取名v2吧** ``` docker build -t nacoslearn:v2 . ``` ![](https://img.tnblog.net/arcimg/aojiancc2/d2b5527da4044429ac26f16a81599b82.jpg) 我们也可以同时在子节点也去打包一个镜像,方便测试 **然后使用命令更新镜像** ``` docker service update --image nacoslearn:v2 nacoslearn ``` ![](https://img.tnblog.net/arcimg/aojiancc2/568ec982265847bbb86fead5ad604dc0.png) **更新之后就可以看到子节点里边也有运行的副本了** ![](https://img.tnblog.net/arcimg/aojiancc2/562583be1e5b4a9e8b612f9c24ef4045.png) 我们在子节点也打包了镜像的,所以子节点能找到这个镜像,就能正确的跑起来了 ## 动态扩容与动态缩容 非常简单和k8s类似,使用命令动态指定副本数量即可 ##### 通过docker service scale命令动态扩容 ``` docker service scale nacoslearn=9 ``` 这样副本数量就从5个变成了9个 #####通过docker service scale命令动态缩容 ``` docker service scale nacoslearn=2 ``` 这样副本数量就降低到了2个 ## 访问集群,负载均衡的访问集群 直接通过两台电脑的ip地址去访问都是没有问题的: ![](https://img.tnblog.net/arcimg/aojiancc2/a5a5856c894f40468957e6ea0b84ffdf.png) ![](https://img.tnblog.net/arcimg/aojiancc2/6219d50bf13d4ae3a58b4c450f9f11c7.png) **调用一下接口也是没有问题的,这里接口我们获取了一下所有网卡的ip地址** ![](https://img.tnblog.net/arcimg/aojiancc2/c72207fb63b34ecf95737255fe394681.png) 这里接口我们获取了一下所有网卡的ip地址 ``` [HttpGet] public IEnumerable<string> Get() { //获取配置中心nacos的DbConfig节点 var dbConfig = _configuration.GetSection("DbConfig"); //读取连接字符串配置 string connectionString = dbConfig["ConnectionString"]; //读取配置 string isAutoCloseConnection = _configuration["DbConfig:IsAutoCloseConnection"]; //读取common里边的配置 string UserName = _configuration["UserName"]; string Password = _configuration["Password"]; //获取服务器上所有网卡的IP地址 NetworkInterface[] networks = NetworkInterface.GetAllNetworkInterfaces(); string serverIpAddresses = string.Empty; foreach (var network in networks) { var ipAddress = network.GetIPProperties().UnicastAddresses.Where(p => p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address)).FirstOrDefault()?.Address.ToString(); serverIpAddresses += network.Name + ":" + ipAddress + "|"; } return new List<string>() { connectionString, isAutoCloseConnection, serverIpAddresses }; } ``` **这个时候我们访问接口本身就是直接负债均衡了的** 通过swagger请求接口有缓存,我们直接使用curl测试,可以看到每次请求返回的ip地址都不一样,所以每次请求的都是不通的容器 ![](https://img.tnblog.net/arcimg/aojiancc2/eb504f500d7c4c0cabf53fc03dd9c437.png) 所以这里我们直接请求接口就已经是支持了负债均衡了的,不需要在通过nacos去进行一次服务发现后调用,因为docker swarm其实是内置了服务注册,服务发现了的,好像是用的consul,而且也是内置了vip以及dns这些。 **大概的原理如下:** ![](https://img.tnblog.net/arcimg/aojiancc2/eb4f71e8216d49bdb90ea1691f0a2c31.png) tn2>在结构图可以看出 Docker Client使用Swarm对 集群(Cluster)进行调度使用。 上图可以看出,Swarm是典型的master-slave结构,通过发现服务来选举manager。manager是中心管理节点,各个node上运行agent接受manager的统一管理,集群会自动通过Raft协议分布式选举出manager节点,无需额外的发现服务支持,避免了单点的瓶颈问题,同时也内置了DNS的负载均衡和对外部负载均衡机制的集成支持 其中Node的图解: ![](https://img.tnblog.net/arcimg/aojiancc2/e81ff3d6ffa346ef97897dc797db5968.png) **可以通过命令查询Swarm中服务的详细信息** ``` docker service inspect --pretty nacoslearn ``` ![](https://img.tnblog.net/arcimg/aojiancc2/eb707cab08d6428dbd133b48ff114e0a.png) 可以看到绑定的ip,使用的模式vip,对外提供服务ingress,使用的网络nacoslearn_net等。 这里用到了inggress。Swarm 管理节点会利用 ingress 负载均衡以将服务公布至集群之外。 **集群的容器之间,也是可以通过ip或者容器名,容器id进行访问的** 比如我们随便进入一个容器内部 ``` docker exec -it 53a6dc1cfc3a /bin/bash ``` **然后可以ping一下其他容器的ip:** ![](https://img.tnblog.net/arcimg/aojiancc2/4e4adca6130848339c27d1120f1f4133.png) 没有问题 **通过容器名称去访问也是没有问题的:** ![](https://img.tnblog.net/arcimg/aojiancc2/efac56a49d1440e4a77a33471f86e816.png) **访问一下接口:** ![](https://img.tnblog.net/arcimg/aojiancc2/0a4e85e845904edd8c3cd4d73938463a.png) 没有问题 如果docker内部的ping命令与curl无法访问的情况下,就在docker内部装一下 ``` apt-get update ## 安装ping命令 apt install iputils-ping ## 安装curl命令 apt install curl -y ``` ## 通过nacos服务发现,负载均衡的访问集群。多网卡下nacos获取ip的配置 虽然这里直接访问接口本身就是支持负债均衡了的,不需要在使用nacos的服务发现了,但是如果我们需要使用也是完全可以的。 **通过SelectInstances方法就可以发现多个服务了,然后在随机调用即可** ``` /// <summary> /// 通过nacos的服务发现来调用接口 /// </summary> /// <returns></returns> [HttpGet("test")] public async Task<string> Test() { // 通过分组与服务名获取 //var instance = await _svc.SelectOneHealthyInstance("BaseApi", "DEFAULT_GROUP"); try { //获取多个实例,可以进行负载均衡等方式调用服务 var instanceList = await _svc.SelectInstances("TNBLOG.BaseApi", "DEFAULT_GROUP", true); if (instanceList.Count == 0) return "未找到相关接口信息"; //使用随机数模拟一下负载均衡 Random random = new Random(); var instance = instanceList[random.Next(0, instanceList.Count)]; var host = $"{instance.Ip}:{instance.Port}"; var baseUrl = instance.Metadata.TryGetValue("secure", out _) ? $"https://{host}" : $"http://{host}"; if (string.IsNullOrWhiteSpace(baseUrl)) { return "empty"; } //测试调用一下上面那个接口 var url = $"{baseUrl}/api/WeatherForecast"; using (HttpClient client = new HttpClient()) { var result = await client.GetAsync(url); return await result.Content.ReadAsStringAsync(); } } catch (Exception ex) { return ex.Message; } } ``` **但是这里要注意nacos获取ip默认是第一个网卡的**,我们这里其实应该是eth1网卡的ip才是我们需要的那个ip地址,通过它才能正确的在容器间调用: ![](https://img.tnblog.net/arcimg/aojiancc2/55b47985328647a1a5a53bef41d3a1c0.png) **多网卡下nacos可以通过配置PreferredNetworks=10.0.2,使服务获取内网中前缀为10.0.2的IP。** ![](https://img.tnblog.net/arcimg/aojiancc2/660629ea7cfc4eae955c7b466afac3d8.png) **修改配置后我们可以重新打包一个v3的镜像,然后动态升级一下镜像** ![](https://img.tnblog.net/arcimg/aojiancc2/a4e57f3e3d394972af8ef0ea924f9934.png) 可以看到nacos获取的就是10.0.2开头的了 ![](https://img.tnblog.net/arcimg/aojiancc2/060b536246454f79b0a606cfbd230764.png) 当然这里其实只是修改了配置不一定需要重新打开镜像,重启一下原有的服务也是可以的,我这里使用更新镜像的方式测试。 **然后我们就可以通过nacos的服务发现去调用接口** ![](https://img.tnblog.net/arcimg/aojiancc2/98eadd0900224a6faf67ec23fdf9922d.png) 多点几下,会发现会随机调用我们跑的5个副本,因为我们获取到服务实例后,生成的随机数去随机调用的,效果如下: ![](https://img.tnblog.net/arcimg/aojiancc2/e91b9e605c1a49a0a630e8719eb607fc.png) ![](https://img.tnblog.net/arcimg/aojiancc2/37ec9fbab84542b3bf990ec727f3dae8.png) **上面我们是使用的指定前缀的方式在多网卡下获取需要的ip地址,当然还有一些其他配置方法比如指定网卡什么的,多在网上搜一下就知道了,不过.net的资料真的很少哇,java的倒是很多-。-** 比如指定网卡可以类似这样配置: ``` "NetworkInterface": "eth1" ``` ![](https://img.tnblog.net/arcimg/aojiancc2/f17b902a806c4f8eb955806839972bbb.png) 我测试好像这样指定网卡并不是非常准确,还是有部分服务获取到的是10.0.0.x这种第一个网卡的地址,估计是这里配置.net下的写法有点小区别,找一下确认一下就行了,思路就是这样,.net下的文档也是非常少。 **还可以配置忽略网卡什么的,比如** ``` IgnoredInterfaces[0]=eth0 # 忽略网卡,eth0 IgnoredInterfaces=eth.* # 忽略网卡,eth.*,正则表达式 ```