Una solución para el rápido despliegue de aplicaciones tradicionales

Introducción a los antecedentes

Tenemos un proyecto web y el método de implementación es implementación distribuida lograda por una sola aplicación (dotnet sin núcleo) + balanceador de carga (nginx). Debido al reciente aumento en el número de usuarios, el número de máquinas de carga ha aumentado de 2 a 8. Esto crea otro problema. Cada vez que agregue una máquina, actualice el software o revierta la versión, será muy problemático y doloroso. Ya no podrá pescar correctamente y su vida se desperdiciará en tareas repetitivas con sin contenido técnico superior.

Algunos veteranos pueden haber dicho, ¿por qué no deberíamos crear un sistema de arquitectura de microservicios? Utilice Kubernetes para mostrar sus habilidades, lo que le permitirá gestionar fácilmente cientos o miles de grupos de máquinas. Mientras habla y se ríe, todo desaparecerá. ¿No será hermoso?

Todo lo que puedo decir es, Tiezi, también quiero disfrutar del placer de controlar todo con una mano, pero la situación actual no lo permite todavía, pero ya me estoy desarrollando activamente en esa dirección.

El agua lejana no puede calmar la sed cerca. La situación actual requiere urgentemente una solución semitemporal para resolver este problema de duplicación de trabajo.

Como no existe una solución ya preparada, ¡cree una usted mismo!

La vida entera

Proceso manual

Piénselo cuando la carga sea liviana, todo el proceso de actualización del sitio generalmente incluye los siguientes enlaces.

  • Para evitar errores de actualización, antes de actualizar, haga una copia de seguridad de la versión del sitio original en ejecución estable. Por supuesto, no es necesario realizar una copia de seguridad de todos los archivos, lo que causará presión en el almacenamiento. Solo se realiza una copia de seguridad de los archivos clave según la situación real, pero en cuanto a qué archivos clave, debe determinarse de acuerdo con la situación cada vez.
  • Empaquetar archivos recién publicados
  • Subir al servidor y reemplazar el archivo original.
  • Si cada actualización incluye archivos del lado del servidor, debe reiniciar el sitio iis (actualmente nuestro negocio del lado web se ejecuta en iis, pero en realidad debería extenderse a apache, tomcat, nginx y otros servidores del sitio)

Proceso de operación automática

Después de ordenar todo el proceso de implementación de la actualización, lo he organizado en las siguientes etapas:

  • Monitoreo: en esta etapa, el programa monitorea regularmente los cambios en un determinado archivo. El archivo que configuré aquí es un archivo de formato json, que registra el número de versión y la dirección de descarga del nuevo archivo. Cada vez que se produce un cambio en el número de versión detectado, comenzar a automatizar procesos posteriores;
  • Descargar archivo: esta parte es para descargar el archivo a la ubicación especificada según la dirección de descarga obtenida durante la etapa de escucha;
  • Pausar el sitio (opcional): dado que no todas las actualizaciones requieren reiniciar el sitio, este paso es opcional;
  • Copia de seguridad: según los parámetros de copia de seguridad entrantes, realice una copia de seguridad de archivos y genere registros de copia de seguridad;
  • Descompresión: una vez completada la copia de seguridad, puede descomprimir el segundo archivo descargado y completar el reemplazo de los archivos nuevos y antiguos;
  • Reinicie el sitio (opcional): si el sitio está en pausa, debe reiniciarlo nuevamente después de completar la descompresión y la transición;
  • Notificación: una vez completado todo el proceso de actualización, se debe enviar una notificación por correo electrónico al personal de desarrollo, operación y mantenimiento relevante para informarles la información clave de esta actualización automática.

Después de aclarar los procesos principales anteriores, ¡puede comenzar a codificar!

Lo que uso aquí es la estructura de la consola y uso varios paquetes de herramientas importantes, incluidos Coravel, Serilog, SharpCompress, Hosting, etc.

La dirección del código completo se proporciona al final del artículo.

monitor

Debido a que es un programa de consola, para monitorear la acción de acceder a las URL como un programa web, debe introducir el paquete Microsoft.Extensions.Hosting.

Después de la introducción, puede combinarlo con Coravel para realizar un seguimiento programado.

public static void UseCoravelService(string[] args)
{
    // Renders each item with own style
    bool intervelFlag = true;
    var process = new Rule().RuleStyle("#FDE047");
    var host = new HostBuilder()
         .ConfigureAppConfiguration((hostContext, configApp) =>
         {
             configApp.AddEnvironmentVariables(prefix: "PREFIX_");
             configApp.AddCommandLine(args);
         })
         .ConfigureServices((hostContext, services) =>
         {
             // Add Coravel's Scheduling...
             services.AddScheduler();
         })
         .Build();

    host.Services.UseScheduler(scheduler =>
        scheduler
        .Schedule(async () =>
        {
            //核心业务
            //...
        })
        .EveryFifteenSeconds()
    );
    host.Run();
}

Después de introducir el proxy de red y Coravel, la operación posterior es el código comercial.

/// <summary>
/// 监听
/// </summary>
internal class StepMonitor
{
    public static async Task<bool> getJsonFile(string address)
    {            
        try
        {
            if (string.IsNullOrEmpty(address))
                address = "<我这里给了一个默认地址,也可以不要>";
            HttpClient hc = new HttpClient();
            var content = await hc.GetStringAsync(address);
            var json = JsonHelper.JsonDeserialize<VersionModel>(content);
            string versionFile = "currVersion.txt";
            string currVersion = "0";

            currVersion = await MainScheduling.ReadFile(versionFile);
            if(currVersion.Trim()==json.Version) 
            {
                return false;//线上版本和本地版本相同,继续等待;
            }
            
            await MainScheduling.WriteFile("currVersion.txt", json.Version);
            await MainScheduling.WriteFile("DownloadList.json", JsonHelper.JsonSerialize(json.Items));
            return true;
        }
        catch (Exception ex)
        {
            AnsiConsole.WriteLine($"[red]下载更新索引文件失败,{ex.Message}[/]");
            throw;
        }

    }
}

Después de definir los métodos de monitoreo relevantes, puede volver a Programar y llamarlos.

host.Services.UseScheduler(scheduler =>
        scheduler
        .Schedule(async () =>
        {
            string cache = CacheManager.Default.Get<string>("step");
            AnsiConsole.MarkupLine($"[#1E9FFF]当前状态:【{cache}】,{DateTime.Now.ToString()}[/]");
            if (cache == "waiting")
            {
                intervelFlag = false;
                OutputStep(0, "监听中(waiting),正在执行...");
                string file = GetParamValue(args, "file");
                if (await StepMonitor.getJsonFile(file))
                {
                    //CacheManager.Default.Set_SlidingExpire<string>("step", "download", TimeSpan.FromMinutes(10));
                    await SetStep("download");
                }
                intervelFlag = true;
            }
        })

El efecto de seguimiento final es el siguiente

0a1229313e7688cd09e543bba68dea07.png

descargar

No hay mucho que decir sobre esta parte. Simplemente puede descargar el archivo de la forma que desee. No es imposible incluso llamar a herramientas de modelado como wget. Solo necesita hacer un buen trabajo interactuando con la información de el programa.

No publicaré el código de descarga aquí. Puedes verlo en el código fuente. La captura de pantalla es la siguiente.

ca1d62e533538b8a846594a29b53981d.png

 

Cerrar sitio (opcional)

Juzgo si debo cerrar el sitio aquí en función de si el archivo de versión contiene la palabra clave estática.

string currVersion = await ReadFile("currVersion.txt");
if (currVersion.Contains("static"))
{
    OutputStep(2, "仅更新静态文件,无需重启站点");
}
else
{
    OutputStep(2, "站点关闭中(stopweb),正在执行...");                            
    string appSite = GetParamValue(args, "appSite");
    string appPool = GetParamValue(args, "appPool");
    StepIISManager.Stop(appSite, appPool);                                                    
}

Las capturas de pantalla de las dos situaciones son las siguientes.

 

2acbd9f5e29b5110e29b61d657749d0a.png

45ae0898e89be29028930798c789f268.png

archivo de respaldo

Para los archivos de respaldo, he configurado un parámetro de entrada especial "-subpath" aquí. Si no se pasa este parámetro, se realizará una copia de seguridad completa del sitio. Si se pasa, la copia de seguridad se basará en los archivos pasados ​​o directorios.

//这里的参数是解压地址
string inputPath = GetParamValue(args, "output");
string outputPath = GetParamValue(args, "backuppath");

string currVersion = await ReadFile("currVersion.txt");
string subPath = GetParamValue(args, "subpath");
OutputStep(3, "站点备份中(backup),正在执行...");
if (!string.IsNullOrEmpty(subPath))
{
    AnsiConsole.MarkupLine("[#FDE047]检测到子目录参数,不再进行全量备份,正在顺序备份[/]");
    if (!string.IsNullOrEmpty(subPath))
    {
        string[] parts = subPath.Split(',');
        var paths = parts.Where(u => !u.Contains(".")).ToList();
        var files = parts.Where(u => u.Contains(".")).ToList();
        if (files.Any()&&paths.Any(u=>!u.Equals("_temp")))
        {
            paths.Add("_temp");
        }
        foreach(var file in files)
        {
            AnsiConsole.MarkupLine($"[#FDE047]文件{file}备份中...[/]");
            string subInputPath = Path.Combine(inputPath, file);
            if (File.Exists(subInputPath))
            {
                string tempPath = Path.Combine(inputPath, "_temp");
                if (!Directory.Exists(tempPath))
                {
                    Directory.CreateDirectory(tempPath);
                }
                File.Copy(subInputPath, Path.Combine(tempPath, file), true);                                                                                
            }
        }
        foreach (var item in paths)
        {
            
            AnsiConsole.MarkupLine($"[#FDE047]{item}备份中...[/]");
            string subInputPath = Path.Combine(inputPath, item);                                    
            await StepDeCompress.ZipCompress(subInputPath, outputPath, item);
        }
    }
}
else
{
    await StepDeCompress.ZipCompress(inputPath, outputPath);
}

6e773434141f2bdf78f3c886a46d1bf7.png

 

Abrir la cremallera

La descompresión consiste en descomprimir el archivo descargado en el segundo paso en el directorio especificado, asumiendo la capacidad del componente Sharpcompress.

/// <summary>
/// 解压文件
/// </summary>
/// <param name="inputFile">解压文件路径</param>
/// <param name="outputFile">解压文件后路径</param>
public static void Decompression(string inputFile, string outputFile)
{
    try
    {
        if (!inputFile.Contains(":"))
        {
            inputFile = Path.Combine(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName), inputFile);
        }
        SharpCompress.Readers.ReaderOptions options = new SharpCompress.Readers.ReaderOptions();
        options.ArchiveEncoding.Default = Encoding.GetEncoding("utf-8");
        var archive = ArchiveFactory.Open(inputFile, options);
        AnsiConsole.Status()
            .Start("解压文件...", ctx =>
            {
                foreach (var entry in archive.Entries)
                {
                    if (!entry.IsDirectory)
                    {                        
                        entry.WriteToDirectory(outputFile, new ExtractionOptions { ExtractFullPath = true, Overwrite = true });
                    }
                }
                AnsiConsole.MarkupLine("[#1E9FFF]解压完成[/]");
            });
    }
    catch (Exception ex)
    {
        AnsiConsole.WriteLine($"[red]{ex.Message}[/]");
        throw;
    }
}

representaciones

f6a30980c4a55eb22a1be0b2dd39ed03.png

 

Reiniciar sitio (opcional)

Los mismos pasos que para pausar el sitio, excepto que el comando entrante se convierte en iniciar

if (currVersion.Contains("static"))
{
    OutputStep(5, "仅更新静态文件,无需重启站点");
}
else
{
    OutputStep(5, "站点启动中(startweb),正在执行...");                            
    string appSite = GetParamValue(args, "appSite");
    string appPool = GetParamValue(args, "appPool");
    StepIISManager.Start(appSite, appPool);
    Console.WriteLine(DateTime.Now.ToString());
}

Efecto

6bcad7bf550556a31f6962cb7a61cff7.png

 

Enviar notificación

En realidad, esta parte es evidente. Se trata de enviar un correo electrónico al administrador designado. El contenido del correo electrónico debe incluir los detalles de la actualización.

No publicaré el código aquí, echemos un vistazo a las representaciones.

ac36f149aa68988b468ee10a6f725971.png

 

3927d101fdadac9e5824a49f9e9b9032.png

Resumir

Aunque ahora estamos en la era de la nube nativa y están surgiendo uno tras otro varios marcos que recurren a los microservicios, un hecho que no se puede ignorar es que las aplicaciones monolíticas todavía representan una proporción considerable en la actualidad. Especialmente en los proyectos de pequeñas y microempresas e incluso en bastantes medianas y grandes empresas, las aplicaciones monolíticas aún deberían representar la corriente principal. Debido a las limitaciones de su propio negocio, algunas empresas pueden no ser adecuadas para ser transferidas a las filas de Microservicios desde el nacimiento hasta la muerte. Esta es la realidad y también una lástima.

Mi actitud personal hacia los microservicios es básicamente la de un creyente y estoy adoptando activamente la transformación. El líder del equipo también nos apoya para convertir el negocio existente en una arquitectura de microservicios paso a paso en función de la situación real. Sin embargo, después de todo, Es un proyecto que se ha puesto en producción y todavía necesitamos. La estabilidad es nuestra principal prioridad, por lo que avanzamos mucho más lento. Un gran número de empresas todavía funcionan basándose en la estructura de una única aplicación, por lo que las principales tareas de desarrollo y mantenimiento siguen basándose en el negocio real. Somos cautelosamente optimistas sobre la transformación. Creo que esta es una actitud objetiva, positiva y correcta. La innovación en sí misma es algo fascinante con un futuro incierto, pero involucrarse demasiado o incluso realizar algunas operaciones extremas causará riesgos impredecibles. Partir de la realidad, combinar las condiciones comerciales y avanzar paso a paso es el camino correcto. También creo que en 2023 finalmente daremos el paso más crítico en el camino hacia la transformación.

 

adjunto

Dirección del código: Para ti mismo_Bring Salt/AutoDeploy · GitCode

 

Supongo que te gusta

Origin blog.csdn.net/juanhuge/article/details/128677380
Recomendado
Clasificación