返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
StarBlog博客开发笔记

Developing a blog project based on .NET Core StarBlog - (31) Publishing and Deployment

Deployment? Same old story. .NET 8 AOT is hyped to the skies, but StarBlog hasn't adopted it yet—you can only choose between framework-dependent or self-contained. Supervisor, Docker, pipelines—pick any, they're all equally troublesome.

曦远 (DealiAxy)约 26 分钟
目录23 个章节

Preface

The features planned for the first phase of StarBlog are basically complete. I was thinking that before the Chinese New Year, I should be able to wrap up the first series of articles. Therefore, in the stage of filling in the gaps, the last step remaining for the project is deployment.

PS: In fact, there is a very important but often overlooked testing phase that we haven't mentioned. Due to time constraints, I didn't write unit tests or integration tests in the first phase planning. In development, the testing phase is essential. I'm also setting a flag that I will supplement this part of the test code later.

Regarding dotnet testing, you can check out my previous article. For those with spare time, you can first try writing the test code yourself.

Article Link: ASP.NET Core Learning Notes: Unit Testing and Integration Testing

C# has now fully embraced cloud-native, cross-platform development, offering a variety of deployment methods. Particularly after the release of dotnet 8, the AOT (Ahead-of-Time) functionality has been significantly enhanced, providing convenience in AOT capabilities comparable to Go. This article introduces several common deployment options for readers to choose flexibly.

Publishing

Publishing is the process of compiling and packaging a program into an executable file, typically using the dotnet publish or msbuild command. This article uses dotnet publish.

There are also multiple compilation modes, which are generally categorized as follows:

  • Framework-dependent - Requires the corresponding version of the runtime to be installed beforehand in order to execute, similar to Java's jar packages.
  • Self-contained - Includes the runtime itself and can be executed directly, but there is no fundamental difference compared to framework-dependent.
  • AOT (Ahead of Time) - Generates a binary file that can run directly without requiring a runtime.

The third mode, AOT, was newly introduced with .NET 8 (it was not fully developed in earlier versions). Currently, for AspNetCore projects, some modifications are still required. At the time of developing this project, AOT was not yet available, so we will only use the first two methods. In the future, I will look into publishing StarBlog using the AOT method.

Framework-Dependent Publishing

Use the command

dotnet publish -r linux-x64 -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained false

Parameter Explanation:

  • -r linux-x64 - Indicates using the linux-x64 runtime. If publishing to another platform, change this to the corresponding runtime, e.g., win-x64.
  • -c Release - Uses the Release configuration for the build. The alternative is the Debug mode, which is only for development.
  • -p:PublishSingleFile=true - Generates a single file. If not configured, it will include all project dependencies, resulting in a large number of DLL files.
  • -p:PublishTrimmed=true - Trims the generated file. Although the project has many dependencies, the number of classes and methods actually used is much smaller. This unused part can be removed. However, if the project extensively uses reflection, use this feature with caution, as the compiler may not be able to check for dynamically invoked methods.

Self-Contained Publishing

Using the command

dotnet publish -r linux-x64 -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true

will generate the following files:

 publish
 ├─ wwwroot
 ├─ words.json
 ├─ StarBlog.Web.xml
 ├─ StarBlog.Web
 ├─ SQLite.Interop.dll
 ├─ package.json
 ├─ package-lock.json
 ├─ Magick.Native-Q8-x64.dll.so
 ├─ Magick.Native-Q8-x64.dll.dylib
 ├─ Magick.Native-Q8-x64.dll
 ├─ libe_sqlite3.so
 ├─ appsettings.json
 ├─ appsettings.Development.json
 └─ appsettings-email.json

1 directory, 13 files

The executable file here is StarBlog.Web, which is our project name.

The rest are configuration files and such. You can also see several DLLs. Didn't we say earlier that all DLLs are packaged into the executable? Why are there still some? That's incorrect—what gets bundled together are the DLLs implemented purely in C# (C# 实现的 dll), which contain IL code. Meanwhile, SQLite.Interop.dll and Magick.Native-Q8-x64.dll are actually third-party libraries (第三方库) developed in C/C++ or similar (C/C++ 之类开发的). These are in binary form, and by default, this part is not included in the executable.

Of course, they can be integrated as well; simply adding the parameter -p:IncludeNativeLibrariesForSelfExtract=true will suffice.

On-Premises Deployment (supervisor)

The most straightforward deployment method is to publish the program as an executable, run it directly on the server, and use a process monitoring tool like supervisor. This is also the deployment method currently used for my blog.

I've previously written an article about deployment, which you can refer to: Asp-Net-Core Study Notes: 5. Build and Deployment

Using supervisor to manage processes

First, install it.

pip install supervisor

Create a configuration file under /etc/supervisord.d/, for example, star_blog.ini

The content is as follows, for reference. Once running, it can be accessed on port 8080.

[program:star_blog]

Script Directory

directory = /home/apps/StarBlog

Script Execution Command

command = /home/apps/StarBlog/StarBlog.Web --urls "http://*:8080;"

Whether supervisor starts simultaneously by default, True

When a program exits, it does not automatically restart by default. By default, if it exits unexpectedly, the system can be configured to automatically restart after a subprocess failure.

There are three options: false, unexpected, and true.

If set to false, the process will never be restarted under any circumstances. If set to unexpected, the process will only restart when its exit code is not defined in the exitcodes list.
autostart = true

This option specifies the number of seconds after the subprocess starts. If the state is still "running" at that point, we consider the startup successful. The default value is 1.

startsecs = 1

User Identity for Script Execution

user = hello


Then start the supervisor

```bash
systemctl start supervisord.service

Enable auto-start

systemctl enable supervisord.service

The commands may vary for different Linux distributions; you can consult the documentation for details: https://github.com/Supervisor/supervisor

Docker Deployment

Docker deployment is very flexible. You can package the image locally, upload it to a repository, and pull it onto the server for deployment using docker pull. This approach also integrates smoothly with pipelines later on. Alternatively, you can use my method: using Docker as a runtime environment, then packaging a framework-dependent executable locally and uploading it for execution.

Here, I reference the Docker deployment method from the official template. For details, you can refer to this article I wrote earlier: Asp-Net-Core Development Notes: FrameworkDependent Deployment with Docker

Based on the framework-dependent publishing mentioned earlier, prepare the Docker configuration. From then on, each update only requires uploading the executable file to overwrite the existing one.

Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM base AS final
WORKDIR /app
COPY . .
ENTRYPOINT ["./StarBlog.Web"]

docker-compose.yaml

version: '3.6'

services:
  web:
    image: ${DOCKER_REGISTRY-}web
    container_name: starblog
    restart: always
    environment:
      - ASPNETCORE_ENVIRONMENT=Production
      - ASPNETCORE_URLS=http://+:80
    build:
      context: .
    volumes:
      - .:/app
    ports:
      - "8080:80"
    networks:
      - default

networks:
  default:
    name: starblog

Start

docker compose up

Then the service is accessible at port 8080.

Pipeline Deployment

Pipelines enable automated deployment, freeing up your hands.

This article will not elaborate on this topic. I have previously written detailed articles on it, and interested readers can refer to them:

Additional Notes

Getting Correct IP Address When Using Nginx Reverse Proxy or Inside Docker

This project features visitor tracking functionality. If you're using Nginx reverse proxy or deploying inside Docker without proper configuration, the visitor's IP address will be recorded as 127.0.0.1. This needs to be configured.

Nginx Configuration

server {
    listen        80;
    server_name   example.com *.example.com;
    location / {
        proxy_pass         http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

Backend Middleware Configuration

app.UseForwardedHeaders(new ForwardedHeadersOptions {
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

Done

Summary

Deployment is a common topic, and each method has its pros and cons. I'm in a hurry to wrap things up, so I won't go into too much detail this time~

References

_EOF_

Developing a blog project based on .NET Core StarBlog - (31) Publishing and Deployment

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

StarBlog博客开发笔记(19):Markdown渲染方案探索

前言 笔者认为,一个博客网站,最核心的是阅读体验。 在开发StarBlog的过程中,最耗时的恰恰也是文章的展示部分功能。 最开始还没研究出来如何很好的使用后端渲染,所以只能先用Editor.md组件做前端渲染,过渡一下。前端渲染我是不满意的,因为性能较差,页面加载出来还会闪一下,有割裂感,影响体验。 现在我已经做出了比较完善的后端渲染方案,前端渲染就可以直接退休了。本文介绍StarBlog博客开发

2022-10-28 · StarBlog博客开发笔记 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…

Developing a blog project based on .NET Core StarBlog - (31) Publishing and Deployment · StarBlog