栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

将.net5程序封装到docker镜像

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

将.net5程序封装到docker镜像

文章目录
  • 环境
  • 需求
  • 实现方法
  • RabbiteMQ操作类
    • 主要操作类 QueueOperator
    • 异常类 QueueCanNotDoException
  • 生产者(桌面程序 不记录)
  • 消费者
    • Program 代码
    • Worker 代码
    • Dockerfile
  • 制作镜像
    • docker build 命令
  • 运行镜像
  • 遇见的问题
    • 问题1 docker run -v 挂在路径无效问题(在window环境中)
    • 问题2 visual studio 容器工具首次加载太慢 vsdbgvs2017u5 exists, deleting 太慢或者卡在这里失败
    • 问题3 The framework 'Microsoft.AspNetCore.App', version '5.0.0' was not found. (运行docker内的程序提示框架库找不到)

环境
  • .net 5
  • Docker Desktop Version 3.3.3 (64133) - May 6 2021
  • RabbitMQ 3.8.16 (部署在docker中 部署文档)
需求

一个生产者将生产出来的数据发布到mq中,又多个消费者消费数据,并将消费结果反馈给mq,另外一个消费者消费结果数据。

实现方法

一个Topic的mq,生产者将数据发给交换机,消费者绑定交换机,然后将结果上报给一个简单的mq,另外一个消费者消费结果。

RabbiteMQ操作类 主要操作类 QueueOperator
using System;
using System.Text;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace Common.MQ
{
    public class QueueOperator : IDisposable
    {
        /// 
        /// 消息到达事件
        /// 
        public event Action MessageArrived;
        /// 
        /// 消息被拒绝事件
        /// 
        public event Action MessageDenied;

        /// 
        /// 当不能执行触发异常时的回调
        /// 
        public event Action QueueCanNotDoneed;

        private ConnectionFactory factory;
        private IConnection connection;
        private bool isFirst = true;

        public QueueOperator(string username, string pwd, string db, string hostname, int port)
        {
            factory = new ConnectionFactory()
            {
                UserName = username,
                Password = pwd,
                VirtualHost = db,
                HostName = hostname,
                Port = port
            };
        }

        /// 
        /// 异步发送消息
        /// 
        /// 
        /// 
        /// 
        /// 
        public void SendTopicAsnyc(string exchangeName, string routingkey, TData data) where TData : class, new()
        {
            using (var connection = factory.CreateConnection())
            using (var channel = connection.CreateModel())
            {
                CheckIsFirst(exchangeName, channel);

                channel.BasicAcks += Channel_BasicAcks;
                channel.BasicNacks += Channel_BasicNacks;
                channel.ConfirmSelect();
                var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
                channel.BasicPublish(exchange: exchangeName,
                                     routingKey: routingkey,
                                     basicProperties: null,
                                     body: body);
            }
        }

        public void SendSampleAsnyc(string queueName, TData data)
        {
            using (var connection = factory.CreateConnection())
            using (var channel = connection.CreateModel())
            {
                CreateQueue(queueName, channel);
                channel.BasicAcks += Channel_BasicAcks;
                channel.BasicNacks += Channel_BasicNacks;
                channel.ConfirmSelect();
                var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data));
                channel.BasicPublish(exchange: "",
                                     routingKey: queueName,
                                     basicProperties: null,
                                     body: body);
            }
        }
        private void CheckIsFirst(string exchangeName, IModel channel)
        {
            if (isFirst)
            {
                channel.BasicQos(0, 1, true);
                CreateExchage(exchangeName, "topic", channel);
                // CreateQueue(routingkey, channel);
                // channel.QueueBind(routingkey, exchangeName, routingkey, null);
                isFirst = false;
            }
        }

        /// 
        /// 绑定队列
        /// 
        /// 
        /// 
        /// 
        /// 回调事件,收到数据要做的事情
        public void BindQueue(string exchangeName, string routingkey, Action callback) where TData : class, new()
        {
            connection = factory.CreateConnection();
            var channel = connection.CreateModel();

            CheckIsFirst(exchangeName, channel);
            var queueName = channel.QueueDeclare().QueueName;
            channel.QueueBind(queueName, exchangeName, routingkey);
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                callback?.Invoke(JsonConvert.DeserializeObject(message));
                //反馈处理完成信号
                channel.BasicAck(ea.DeliveryTag, false);
            };
            channel.BasicConsume(queue: queueName,
                         autoAck: false,
                         consumer: consumer);
        }
        /// 
        /// 绑定队列
        /// 
        /// 
        /// 
        /// 回调事件,收到数据要做的事情
        public void BindQueue(string queueName, Action callback) where TData : class, new()
        {
            if (connection == null)
                connection = factory.CreateConnection();
            var channel = connection.CreateModel();
            CreateQueue(queueName, channel);
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                callback?.Invoke(JsonConvert.DeserializeObject(message));
                //反馈处理完成信号
                channel.BasicAck(ea.DeliveryTag, false);
            };
            channel.BasicConsume(queue: queueName,
                         autoAck: false,
                         consumer: consumer);

        }
        private void CreateExchage(string name, string type, IModel channel)
        {
            try
            {
                channel.ExchangeDeclare(exchange: name,
                                 type: type);
            }
            catch (Exception ex)
            {

                QueueCanNotDoneed?.Invoke(new QueueCanNotDoException(ex) { CanNotType = "交换机创建异常" });
            }

        }

        private void CreateQueue(string name, IModel channel)
        {
            try
            {
                channel.QueueDeclare(name, false, false, false, null);
            }
            catch (Exception ex)
            {
                QueueCanNotDoneed?.Invoke(new QueueCanNotDoException(ex) { CanNotType = $"Queue[{name}]创建异常" });
            }

        }
        /// 
        /// 拒绝消息
        /// 
        /// 
        /// 
        private void Channel_BasicNacks(object sender, RabbitMQ.Client.Events.BasicNackEventArgs e)
        {
            MessageDenied?.Invoke(e.DeliveryTag, e.Multiple);
        }
        /// 
        /// 接收消息
        /// 
        /// 
        /// 
        private void Channel_BasicAcks(object sender, RabbitMQ.Client.Events.BasicAckEventArgs e)
        {
            MessageArrived?.Invoke(e.DeliveryTag, e.Multiple);
        }

        public void Dispose()
        {
            if (connection.IsOpen)
                connection.Close();
        }
    }
}

异常类 QueueCanNotDoException
using System;
using System.Collections.Generic;
using System.Text;

namespace Common.MQ
{
    public class QueueCanNotDoException : Exception
    {
        public QueueCanNotDoException(Exception ex)
        {
            new Exception(ex?.Message??"", ex);
        }

        public string CanNotType { get; set; }

        public override string ToString()
        {
            return $"{CanNotType} 不能被执行,详细信息:rn{Message}";
        }
    }
}

生产者(桌面程序 不记录)

生产者这里没有好记录的,正常操作mq就行了

消费者

.net 5 的 work service

Program 代码
  public static IHostBuilder CreateHostBuilder(string[] args)
        {
            var ihostbuilder = Host.CreateDefaultBuilder(args);
            //window和linux的路径表示方式不一样,虽然/在win10中也能识别,但是尽量保持系统原有风格
            var configFile = AppDomain.CurrentDomain.BaseDirectory;
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                ihostbuilder.UseWindowsService();
                configFile += "conf\appsettings.json";
            }
            else
            {
                ihostbuilder.UseSystemd();
                configFile += "conf/appsettings.json";
            } 

            ihostbuilder.ConfigureAppConfiguration((hostingContext, config) =>
            {
                config.Sources.Clear();
                //更改配置文件的路径,方便docker进行外部映射 Volume
                config.AddJsonFile(configFile);
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService();
            })
            .ConfigureLogging(logging =>
            {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Trace);
            })
            .UseNLog(); 
            return ihostbuilder;
        }
Worker 代码
  public class Worker : BackgroundService
    {
        private readonly ILogger _logger;
        private static QueueOperator queueOperator;
        private MQSetting setting;
        public Worker(ILogger logger, IConfiguration configuration)
        {
            _logger = logger;
            setting = configuration.GetSection("MQSetting").Get();
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                queueOperator = new QueueOperator(setting.UserName, setting.Password, setting.DBName, setting.HostName, setting.Port);
                queueOperator.BindQueue>(setting.ExchangeName, setting.RoutingKey, data =>
                {
                    Console.WriteLine(data.Step);
                    queueOperator.SendSampleAsnyc>(setting.ResultRoutingKey, new CarlingResultData() { Step = data.Step, Key = data.Key });
                });
                Console.WriteLine("初始化mq完成");
            }
            catch (Exception ex)
            {
                Console.WriteLine("初始化mq异常:" + ex.Message);
            }


            return base.StartAsync(cancellationToken);
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                Console.WriteLine($"Worker running at: { DateTimeOffset.Now}");
                //  _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
    }
Dockerfile
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
# 这里要注意,vs生成的默认dockerfile中这里的runtime:5.0的版本,这个版本会导致做成镜像无法运行,因为缺少.net 5 库。 
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
WORKDIR /src

RUN dotnet restore
COPY . .

#FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
#WORKDIR /src
#COPY ["service/Shimada.CarlingService/Shimada.CarlingService.csproj", "service/Shimada.CarlingService/"]
#RUN dotnet restore "service/Shimada.CarlingService/Shimada.CarlingService.csproj"
#COPY . .
#WORKDIR "/src/service/Shimada.CarlingService"
#RUN dotnet build "Shimada.CarlingService.csproj" -c Release -o /app/build
#
#FROM build AS publish
#RUN dotnet publish "Shimada.CarlingService.csproj" -c Release -o /app/publish

#FROM base AS final
#WORKDIR /app
#COPY --from=publish /app/publish .
#ENTRYPOINT ["dotnet", "Shimada.CarlingService.dll"]

FROM base AS debuge
WORKDIR /app
COPY "bin/Debug/net5.0/" "/app" 
EXPOSE 8080/tcp
EXPOSE 8080/udp
# 这里将conf配置文件目录和Logs日志文件目录映射出来,方便在docker外面修改和查看数据。
VOLUME ["/app/conf","/app/Logs"]
ENTRYPOINT ["dotnet", "Shimada.CarlingService.dll"]

制作镜像 docker build 命令
docker build -t carlingservice:0.0.5.5 C:shimadaSY-VIS-2101srcSY_VIS_2101serviceShimada.CarlingService

命令语法

Usage:  docker build [OPTIONS] PATH | URL | -

Build an image from a Dockerfile

Options:
      --add-host list           Add a custom host-to-IP mapping (host:ip)
      --build-arg list          Set build-time variables
      --cache-from strings      Images to consider as cache sources
      --disable-content-trust   Skip image verification (default true)
  -f, --file string             Name of the Dockerfile (Default is
                                'PATH/Dockerfile')
      --iidfile string          Write the image ID to the file
      --isolation string        Container isolation technology
      --label list              Set metadata for an image
      --network string          Set the networking mode for the RUN
                                instructions during build (default "default")
      --no-cache                Do not use cache when building the image
  -o, --output stringArray      Output destination (format:
                                type=local,dest=path)
      --platform string         Set platform if server is multi-platform
                                capable
      --progress string         Set type of progress output (auto, plain,
                                tty). Use plain to show container output
                                (default "auto")
      --pull                    Always attempt to pull a newer version of
                                the image
  -q, --quiet                   Suppress the build output and print image
                                ID on success
      --secret stringArray      Secret file to expose to the build (only
                                if BuildKit enabled):
                                id=mysecret,src=/local/secret
      --ssh stringArray         SSH agent socket or keys to expose to the
                                build (only if BuildKit enabled) (format:
                                default|[=|[,]])
  -t, --tag list                Name and optionally a tag in the
                                'name:tag' format
      --target string           Set the target build stage to build.
运行镜像
docker run --name debug2 -d carlingservice:0.0.5.9 -v /C/shimada/SY-VIS-2101/src/SY_VIS_2101/service/Shimada.CarlingService/bin/Logs:/app/Logs
遇见的问题 问题1 docker run -v 挂在路径无效问题(在window环境中)

https://blog.csdn.net/ap10062kai/article/details/79232582
互联网解决办法,在我这里没有解决

我的解决办法:
使用docker desktop工具运行的镜像,指定的挂在路径就可以了,有谁知道用命令怎么写的可以告诉我

2021年5月13日 补充
使用命令可以实现挂载

 docker run -v C:shimadaSY-VIS-2101srcSY_VIS_2101serviceShimada.CarlingServicebinconf:/app/conf -v C:shimadaSY-VIS-2101srcSY_VIS_2101serviceShimada.CarlingServicebinLogs:/app/Logs --name debug3 -d carlingservice:0.0.5.5

重点: -v参数一定要紧跟着run 如果在run和-v中间添加了其他参数会导致执行失败。
desktop版本的docker在添加挂载的时候会有确认提示框,提示是否分享目录。(也可以提前在Setting中配置好File Sharing)

问题2 visual studio 容器工具首次加载太慢 vsdbgvs2017u5 exists, deleting 太慢或者卡在这里失败

可以按照 https://www.cnblogs.com/microestc/p/10784877.html 的办法解决
个人觉得上面这个办法太麻烦了,直接开了个代理,因为国内的墙无法下载一些地址的文件导致的。

问题3 The framework ‘Microsoft.AspNetCore.App’, version ‘5.0.0’ was not found. (运行docker内的程序提示框架库找不到)

在dockerfile中进行修改 (看上面的dockerfile源码)

# 这里要注意,vs生成的默认dockerfile中这里的runtime:5.0的版本,这个版本会导致做成镜像无法运行,因为缺少.net 5 库。 
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base

https://docs.docker.com/engine/reference/builder/#usage (dockerfile 文档)
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#file-configuration-provider (appsettings.json 文档)
https://www.cnblogs.com/microestc/p/10784877.html (使用vs生成镜像失败或者非常慢的解决办法)
https://blog.csdn.net/shanghaibao123/article/details/108278639
https://www.rabbitmq.com/tutorials/tutorial-one-dotnet.html

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/906631.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号