Menjalankan aplikasi .NET sebagai layanan di Linux dengan Systemd

Dalam posting ini, mari kita lihat bagaimana Anda dapat menjalankan aplikasi .NET Core / .NET 5 sebagai layanan di Linux. Kami akan menggunakan Systemd untuk mengintegrasikan aplikasi kami dengan sistem operasi dan memungkinkan untuk memulai dan menghentikan layanan kami, dan mendapatkan log darinya.

Untuk membangun serangan rantai pasokan saya dengan .NET, saya perlu meng-host server DNS untuk menangkap nama host yang dikirimkan kepada saya. Mari kita gunakan itu sebagai contoh!

Membuat aplikasi .NET untuk dijalankan sebagai layanan

Layanan .NET perlu menggunakan Microsoft.Extensions.Hosting model hosting. Ini berarti aplikasi ASP.NET Core apa pun akan berfungsi, seperti halnya proyek yang dibuat menggunakan Layanan Pekerja template (dotnet new worker). Saya akan menggunakan Rider di sini.

Selanjutnya, Anda perlu menginstal paket NuGet: Microsoft.Extensions.Hosting.Systemd. Paket ini menyediakan infrastruktur hosting .NET untuk layanan Systemd. Dengan kata lain: ini berisi infrastruktur untuk bekerja dengan daemon Systemd di Linux.

Satu hal yang tersisa: mendaftarkan ekstensi hosting Systemd di aplikasi kita. Di Program.cs, Anda harus menambahkan .UseSystemd() di pembuat host:

public class Program
{
    // ...

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSystemd() // add this
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<Worker>();
            });
}

Dengan itu, aplikasi kita dapat berjalan sebagai layanan Systemd di Linux! Hmm, hampir… Ada dua hal yang harus dilakukan:

  • Terapkan aplikasinya
  • Daftarkan aplikasi dengan Systemd

Mari kita mulai dengan yang pertama, dan terapkan server DNS yang akan kita jalankan sebagai layanan.

Menerapkan server DNS .NET sebagai layanan

Server DNS yang disebutkan di posting saya sebelumnya dibangun di .NET menggunakan paket DNS yang sangat baik dari Mirza Kapetanovic, dan dihosting sebagai layanan di Linux, menggunakan Systemd.

Mari membangun layanan DNS sederhana yang mengembalikan waktu saat ini sebagai TXT merekam.

Catatan: Anda dapat melewati bagian ini jika Anda hanya ingin mempelajari tentang cara mendaftarkan aplikasi .NET sebagai layanan Systemd.

Pertama, instal DNS paket. Itu datang dengan server DNS yang hampir siap pakai, sehingga kita hanya perlu menghubungkannya dan mengimplementasikan logika server.

Itu Worker kelas yang dibuat oleh template layanan pekerja adalah dasar yang baik untuk memulai. Di dalamnya, Anda dapat menggunakan injeksi konstruktor untuk mendapatkan akses ke layanan apa pun yang tersedia di aplikasi kita. Kami akan pergi dengan ILogger (sudah ada di template), dan IConfiguration untuk mendapatkan akses ke appsettings.json/ variabel lingkungan / argumen baris perintah. Itu meluas BackgroundService, yang memiliki ExecuteAsync metode yang akan menjalankan layanan kami. Ini kerangka Worker:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IConfiguration _configuration;

    public Worker(ILogger<Worker> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // ... logic will go here ...
    }
}

Sekarang mari kita pasang server DNS itu sendiri. Dalam ExecuteAsync metode, tambahkan yang berikut ini:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    var server = new DnsServer(new SampleRequestResolver());
    server.Listening += (_, _) => _logger.LogInformation("DNS server is listening...");
    server.Requested += (_, e) => _logger.LogInformation("Request: {Domain}", e.Request.Questions.First().Name);
    server.Errored += (_, e) =>
    {
        _logger.LogError(e.Exception, "An error occurred");
        if (e.Exception is ResponseException responseError)
        {
            _logger.LogError("Response: {Response}", responseError.Response);
        }
    };

    await Task.WhenAny(new[]
    {
        server.Listen(
            port: int.TryParse(_configuration["Port"], out var port) ? port : 53531,
            ip: IPAddress.Any),

        Task.Delay(-1, stoppingToken)
    });
}

Waktunya membuka!

  • Kami sedang menyiapkan DnsServer, yang akan menyelesaikan rekaman menggunakan file SampleRequestResolver (yang perlu kita bangun).
  • Kami berlangganan beberapa acara yang diekspos oleh DnsServer, sehingga kami dapat melihat log permintaan yang masuk, dan melihat kapan pun terjadi kesalahan.
  • Akhirnya, kami mulai mendengarkan permintaan yang masuk.

saya menggunakan Task.WhenAny di sini untuk memulai server DNS itu sendiri. Alasannya adalah ExecuteAsync lulus a CancellationToken, dan kami ingin dapat mematikan layanan kami saat pembatalan diminta.

Server itu sendiri akan mendengarkan pada port yang ditentukan dalam konfigurasi, dan jika tidak ada, defaultnya adalah 53531.

Satu hal yang tersisa: itu SampleRequestResolver kelas. Itu harus diimplementasikan IRequestResolver, dan buat jawaban untuk kueri DNS yang masuk. Karena ini bukan cakupan sebenarnya dari entri blog ini, berikut adalah penerapan cepat yang mengembalikan tanggal dan waktu saat ini untuk semua TXT catatan diminta, dan mengembalikan kesalahan untuk permintaan lain:

public class SampleRequestResolver : IRequestResolver
{
    public Task<IResponse> Resolve(IRequest request, CancellationToken cancellationToken = new CancellationToken())
    {
        IResponse response = Response.FromRequest(request);

        foreach (var question in response.Questions)
        {
            if (question.Type == RecordType.TXT)
            {
                response.AnswerRecords.Add(new TextResourceRecord(
                    question.Name, CharacterString.FromString(DateTime.UtcNow.ToString("O"))));
            }
            else
            {
                response.ResponseCode = ResponseCode.Refused;
            }
        }

        return Task.FromResult(response);
    }
}

Jika Anda menjalankan layanan secara lokal, Anda akan melihatnya berfungsi! Anda dapat menggunakan alat seperti nslookup dan dig untuk mencobanya.

Gunakan dig untuk memeriksa server DNS .NET kami

Periksalah README.md proyek DNS untuk contoh lainnya, dan berhati-hatilah untuk tidak secara tidak sengaja membuat resolver DNS terbuka jika Anda menjelajahi pembuatan DNS Anda sendiri.

Buat konfigurasi unit layanan

Mari host layanan kami di Linux! Penggunaan Systemd konfigurasi unit layanan file yang menentukan apa yang dilakukan suatu layanan, apakah harus dimulai ulang, dan sebagainya.

Anda harus membuat file .service file di mesin Linux tempat Anda ingin mendaftar dan menjalankan layanan. Dalam bentuknya yang paling sederhana, file layanan terlihat seperti ini:

[Unit]
Description=DNS Server

[Service]
Type=notify
ExecStart=/usr/sbin/DnsServer --port=53

[Install]
WantedBy=multi-user.target

Itu [Unit] bagian menjelaskan informasi yang lebih umum tentang aplikasi kita. Systemd dapat menjalankan lebih dari sekedar layanan, dan [Unit] adalah bagian umum untuk semua jenis aplikasi yang dapat dijalankan. Saya hanya menambahkan Description, tetapi ada lebih banyak opsi yang tersedia di sini.

Dalam [Service] bagian, kami mendefinisikan detail tentang aplikasi kami. Untuk aplikasi .NET, file Type akan notify, sehingga kami dapat memberi tahu Systemd ketika host telah memulai atau menghentikan – file Microsoft.Extensions.Hosting.Systemd paket menangani itu. ExecStart menentukan jalur tempat biner startup layanan kami berada. Dalam contoh di atas, saya akan menggunakan aplikasi .NET mandiri dan memberi tahu port mana yang harus didengarkan sebagai argumen baris perintah.

Terakhir, file [Install] bagian mendefinisikan target OS mana yang dapat memulai layanan kami. Pada kasus ini, multi-user.target memungkinkan memulai layanan setiap kali kita berada dalam lingkungan multi-pengguna (hampir selalu). Anda juga dapat menyetel ini ke graphical.target jadi Anda memerlukan lingkungan grafis yang dimuat untuk memulai layanan ini.

Buat aplikasi .NET mandiri

Layanan kami sendiri harus tersedia di mesin Linux target, tempat kami mendefinisikan Systemd dapat menemukannya (dengan ExecStart): /usr/sbin/DnsServer.

Hal terakhir yang ingin saya lakukan adalah menyebarkan .NET ke mesin target, jadi saya memutuskan untuk membangun aplikasi mandiri, sebagai satu file – runtime .NET dan semua dependensi yang diperlukan digabungkan dalam satu file yang dapat dieksekusi. Saya menggunakan perintah berikut untuk membangun aplikasi:

dotnet publish -c Release -r linux-x64 --self-contained=true -p:PublishSingleFile=true -p:GenerateRuntimeConfigurationFiles=true -o artifacts

Ini menciptakan DnsServer dapat dieksekusi sekitar 62 MB (ini berisi apa yang dibutuhkan dari runtime .NET). Salin ke /usr/sbin/DnsServer di mesin Linux, dan pastikan itu dapat dieksekusi (sudo chmod 0755 /usr/sbin/DnsServer).

Menginstal dan menjalankan layanan di Linux

Itu .service file yang kami buat (saya menamakannya dnsserver.service) harus ada di /etc/systemd/system/ direktori mesin Linux tempat layanan akan digunakan.

Selanjutnya, jalankan perintah berikut sehingga Systemd memuat file konfigurasi baru ini:

sudo systemctl daemon-reload

Ini sekarang memungkinkan untuk melihat status layanan server DNS kami:

sudo systemctl status dnsserver.service

Status layanan yang baru dipasang

Ada beberapa perintah lain yang mungkin berguna:

Selamat, sekarang kami memiliki aplikasi .NET yang dijalankan sebagai layanan, di Linux!

Apa yang terjadi dalam pelayanan saya?

Saat layanan berjalan, Anda mungkin juga tertarik dengan apa yang dilakukannya. Kita dapat memeriksa entri log terbaru yang dikeluarkan aplikasi kita, menggunakan perintah berikut:

sudo systemctl status dnsserver.service

Kami dapat melihat layanan kami berjalan, mendapatkan detail tentang id proses, dan melihat entri log terbaru.

Entri log terbaru dari layanan kami

Jika Anda benar-benar membutuhkan log, Anda dapat menggunakan journalctl, dan dapatkan semua log untuk unit (-u) yaitu server DNS kami:

sudo journalctl -u dnsserver.service

Yang keren adalah, terima kasih kepada Microsoft.Extensions.Hosting.Systemd paket, kami mendapatkan kode warna dan tingkat keparahan log di sini:

Catat keparahan di journalctl

Kesimpulan

Dalam posting ini, kita melihat bagaimana menjalankan aplikasi .NET sebagai layanan Systemd di Linux, menggunakan .NET Microsoft.Extensions.Hosting.Systemd paket. Kami hanya menggaruk permukaannya saja. Ada lebih banyak opsi yang dapat Anda berikan ke Systemd, Anda dapat memfilter level log dengan journalctl, dan lainnya. Saya sarankan untuk melihat blog Niels Swimberghe untuk beberapa contoh lainnya.

tanpa server360