曦远 (DealiAxy)一份私人的文字刊物

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博客开发笔记(25):图片接口与文件上传

前言 上传文件的接口设计有两种风格,一种是整个项目只设置一个接口用来上传,然后其他需要用到文件的地方,都只存一个引用ID;另一种是每个需要文件的地方单独管理各自的文件。这俩各有优劣吧,本项目中选择的是后者的风格,文章图片和照片模块又要能CRUD又要批量导入,还是各自管理文件比较好。 图片接口 说会正题,先介绍一下图片相关接口。 图片列表 首先CRUD是肯定有的,图片列表的分页查看也是有的,不过因为

2023-02-05 · StarBlog博客开发笔记 · 约 1 分钟

讨论

共 0 条

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

加载评论中…