C# cơ bản .NET Core

Kestrel Server trong asp.net core

Kestrel là một máy chủ web đa nền tảng nó được tích hợp sẵn và chạy mặc định trong các ứng dụng asp.net khởi tạo từ các template (Asp.net core mvc, asp.net razor page ... đã biết). Có nghĩa khi bạn build ứng dụng asp.net, chạy nó thì sẽ tự động chạy web server Kestrel

Trong phần này, tìm hiểu một số cấu hình cơ bản để có thể áp dụng làm cơ sở chuẩn bị cho việc publish ứng dụng, triển khai ứng dụng trên môi trường product.

Có thể cấu hình sử dụng Kestrel với hai trường hợp:

Dùng Kestrel trực tiếp nhận các yêu cầu Http gửi đến

Sử dụng Kestrel sau reverse proxy server (IIS, Apache, Nginx) - các reverse proxy server nhận các yêu cầu Http gửi đến, rồi chuyển cho Kestrel

Ở môi trường product thì không nên sử dụng trực tiếp Kestrel mà hãy dùng Reverse Proxy Server (apache,nginx) vì nó cung cấp nhiều chức năng cao cấp cho một máy chủ web, dễ dàng cấu hình https, dễ dàng tích hợp trên hạ tầng có sẵn, cung cấp nhiều lớp bảo mật ...

Cấu hình Kestrel trong ASP.NET core

Khi khởi tạo ứng dụng ASP.NET từ các template, bao giờ cũng có file Program.cs trong đó định nghĩa hàm Main - gọi CreateHostBuilder để chạy ứng dụng, code mặc định này đã sử dụng và chạy Kestrel

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args) 
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Phụ thuộc vào môi trường chạy ứng dụng mà Kestrel chạy và lắng nghe ở các cổng khác nhau theo cấu hình của môi trường. Trong môi trường phát triển, thường nó sẽ lắng nghe ở cổng 5000 (http) và cổng 5001 (https). Cổng này có thể thiết lập qua biến môi trường hệ thống ASPNETCORE_URLS (ví dụ trong khi đóng gói vào docker có thiết lập biến môi trường ASPNETCORE_URLS=https://+:443;http://+:80 thì Kestrel chạy và lắng nghe cổng 443 và 80)

Để tùy biến Kestrel với các thiết lập cụ thể sử dụng đối tượng KestrelServerOptions như sau:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        { 
            webBuilder.UseKestrel(kestrelServerOptions => {
                // Các thiết lập cho Kestrel tại đây
                // sử dụng KestrelServerOptons để thiết lập
            })

            webBuilder.UseStartup<Startup>();
        });

Bind IP - thiết lập Kestrel lắng nghe trên cổng và IP

Bạn có thể sử dụng các phương thức KestrelServerOptions.Listen, KestrelServerOptions.ListenAnyIP, KestrelServerOptions.ListenLocalhost ví dụ:

webBuilder.UseKestrel(kestrelServerOptions => {

    // Thiết lập lắng nghe trên cổng 8090 với IP bất kỳ
    kestrelServerOptions.Listen(IPAddress.Any, 8090);

    // Lắng nghe trên cổng 8091 trên server chạy ứng dụng
    kestrelServerOptions.ListenLocalhost(8091);

    kestrelServerOptions.Listen(IPAddress.Loopback, 8092, listenOptions => {
        // Thiết lập sử dụng SSL - file xác thực SSL testCert.pfx
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });


});

Thiết lập một số giới hạn của Kestrel với KestrelServerLimits

Nếu muốn thay đổi các giới hạn mặc định thì có thể dùng, KestrelServerLimits với các thuộc tính đặt giới hạn, ví dụ thiết lập keep-alive timeout

webBuilder.UseKestrel(kestrelServerOptions => {
    /...

    kestrelServerOptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
});

Các thiết lập khác tham khảo tại: kestrelserverlimits

Nhanh chóng thiết Bind IP cho Server

Bạn có thể nhanh chóng dùng webBuilder.UseUrls để thiết lập IP và cổng của Kestrel, dùng địa chỉ Url để thiết lập Kestrel lắng nghe trên nó. Trong đó có chỉ ra giao thức (http, https), địa chỉ IP, và cổng. Ví dụ:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseUrls("http://0.0.0.0:8090", "https://0.0.0.0:8091");
            webBuilder.UseStartup<Startup>();
        });

Sử dụng Kestrel phía sau các Http Server khác (Http Apache, Nginx, IIS) thì nên sử dụng cách này (UseUrls), và chỉ cần lắng nghe ở giao thức http, còn https được cấu hình ở server phía trước (nginx, apache).

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            // Chỉ nhận http (không https)
            webBuilder.UseUrls("http://0.0.0.0:5000");
            webBuilder.UseStartup<Startup>();
        });

Ngoài ra cũng chú ý, để sử dụng chính xác Kestrel với nginx, apache cần thêm vào đầu Startup.configure

    ...
    // using Microsoft.AspNetCore.HttpOverrides;
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });
    ...

Publish ứng dụng ASP.NET core

Để biên dịch mã nguồn chạy ở môi trường Product, có thể thực hiện các lệnh như sau:

# phục hồi các dependency từ Nuget
dotnet restore

# build
dotnet build -c Release -o app/build

# publish
dotnet publish -c Release -o app/publish

Kết quả là xuất ra ứng dụng ra thư mục app/publish, bạn dùng thư mục này để phân phối - triển khai chạy ứng dụng. Trong thư mục có file dll tên ứng dụng dùng để chạy ứng dụng

dotnet tên-ứng-dụng.dll

Chú ý, server chạy ứng dụng phải cài .NET Core SDK và .NET Core Runtime

Kèm thư mục khi publish

Trong dự án có thể có các thư mục tài nguyên, ví dụ thư mục Uploads, nếu muốn thư mục này được copy vào publish thì trong file: .csproj thêm vào đoạn mã:

  <ItemGroup> 
    <Content Include="Uploads\**"> 
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> 
    </Content> 
  </ItemGroup>

Cài đặt .Net Core SDK trên Server Linux, môi trường chạy ASP.NET

Cài đặt trên CentOS 7

# cài .net3 sdk
sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
sudo yum install dotnet-sdk-3.1 -y
sudo yum install aspnetcore-runtime-3.1 -y
# cài .net6 sdk
sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
sudo yum install dotnet-sdk-6.0 -y
sudo yum install aspnetcore-runtime-6.0  -y

Cài đặt trên CentOS 8

sudo dnf install dotnet-sdk-3.1
sudo dnf install aspnetcore-runtime-3.1

SQL Server

Nếu cần triển khai SQL Server trên Linux thì thực hiện

# Cài đặt MS SQL Server 2017 trên CentOS 7
sudo alternatives --config python
sudo yum install python2 -y
sudo yum install compat-openssl10
sudo alternatives --config python
sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2017.repo
sudo yum install -y mssql-server

# cấu hình phiên bản, password sa
sudo /opt/mssql/bin/mssql-conf setup

Cài đặt trên Ubuntu

wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb

sudo apt-get update; \
  sudo apt-get install -y apt-transport-https && \
  sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-3.1

sudo apt-get update; \
  sudo apt-get install -y apt-transport-https && \
  sudo apt-get update && \
  sudo apt-get install -y dotnet-sdk-3.1

Giám sát ứng dụng ASP.NET với systemd

Giả sử ứng dụng lưu trên Server Linux ở đường dẫn /home/userasp/mvcblog (đã copy nội dung thư mục app/publish vào mvcblog), file chạy ứng dụng là mvcblog.dll, nó được publish và lắng nghe ở cổng http 8090 (cổng này do bạn đặt trong ứng dụng ở phần trên)

Bạn hoàn toàn có thể vào thư mục, chạy ứng dụng với lệnh:

cd /home/userasp/mvcblog/
dotnet mvcblog.dll

Chú ý, bạn tạo ra user linux để chạy ứng dụng (dùng lệnh useradd) - ở ví dụ trên user là userasp

useradd userasp

Lệnh này tạo ra trong hệ thống Linux một user tên userasp, với thư mục home của user này là: /home/userasp/

Khi bạn chạy trực tiếp như vậy, nếu ứng dụng bị crash vì một sự cố nào đó - thì nó sẽ dừng hẳn và không được khởi động lại. Giải quyết trường hợp này hãy sử dụng systemd của Linux, giúp giám sát tình trạng ứng dụng, khởi động ứng dụng nếu chưa chạy hoặc bị crash.

Tạo ra file dịch vụ trong thư mục /etc/systemd/system/, ví dụ ứng dụng là mvcblog, tạo ra file /etc/systemd/system/mvcblog.service (dùng lệnh vi), sau đó biên tập nội dung file này như sau:

[Unit]
Description=Ung dung ASP.NET MVC BLOG

[Service]
WorkingDirectory=/home/userasp/mvcblog
ExecStart=/usr/bin/dotnet /home/userasp/mvcblog/mvcblog.dll
Restart=always
# Khởi động lại ứng dụng sau 10 bị crash
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=asp-net-app
User=userasp
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target

/usr/bin/dotnet là đường dẫn đầy đủ đến file binary của lệnh dotnet, có thể kiểm tra bằng lệnh

which dotnet

Để thiết lập dịch vụ tự động chạy bạn

systemctl enable mvcblog

Khởi chạy dịch vụ (ứng dụng)

systemctl start mvcblog

Xem trang thái

systemctl status mvcblog

Lưu ý, ứng dụng ASP.NET chạy trên lắng nghe ở cổng 8090 hoặc cổng do bạn thiết lập, cổng này không public ra ngoài (không dùng firewall để mở). Cổng chỉ dùng cho các dịch vụ như apache, nginx chuyển hướng đến.

Dùng máy chủ Apache chạy Asp.net trên CENTOS

Đầu tiên hãy đảm bảo máy chủ đã tắt SELinux: Cách tắt SELinux

Nếu chưa có máy chủ Apache, cài vào:

yum -y install httpd mod_ssl mod_rewrite

Dùng lệnh systemctl để chạy, quản lý dịch vụ Apache

systemctl enable httpd
systemctl start httpd

File cấu hình chung của Apache ở đường dẫn: /etc/httpd/conf/httpd.conf

Để cấu hình Apache như một Proxy - chuyển hướng các yêu cầu đến ASP.NET, hãy thêm cấu hình vào, có thể tạo ra file cấu hình trong thư mục /etc/httpd/conf.d/ để Apache tự động nạp

Ví dụ tạo ra file /etc/httpd/conf.d/mvcblog.conf, biên tập nội dung như sau

<VirtualHost *:*>
    RequestHeader set "X-Forwarded-Proto" expr="%{REQUEST_SCHEME}e"
</VirtualHost>

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8090/
    ProxyPassReverse / http://127.0.0.1:8090/
    ServerName mvcblog.vn
    ServerAlias *.mvcblog.vn
    ErrorLog ${APACHE_LOG_DIR}mvcblog-error.log
    CustomLog ${APACHE_LOG_DIR}mvcblog-access.log common
</VirtualHost>

Sau đó khởi động lại HTTPD

systemctl restart httpd

Với cấu hình trên, Http Apache lắng nghe ở cổng 80, khi truy cập với tên miền mvcblog.vn thì nó sẽ chuyển các yêu cầu tới ứng dụng ở cổng 8090. Hãy tạo ra tên miền trong file hosts để kiểm tra.

Cấu hình sử dụng https - Apache centos - cho ứng dụng aps.net core

Để sử dụng SSL tạo giao thức kết nối an toàn https thì bạn cần kích hoạt mod_ssl của Apache, nếu chưa có thì cài đặt và nạp vào cùng Apache

yum install mod_ssl

Khi có mod_ssl có thể tạo Vhost lắng nghe ở cổng 443, bạn cũng nên cài đặt mod_rewrite để tự động chuyển truy cập http thành https

Cấu hình SSL thì cần có cặp private/public key chứng thực bởi bên thứ 3 (như Let's Encrypt), tuy nhiên để kiểm thử ta tự phát sinh cặp key này - tự xác thực (cách phát sinh như hướng dẫn tại Sử dụng OPENSSL )

Thực hiện các lệnh:

mkdir /certtest
cd /certtest/
openssl genrsa -out ca.key 2048
openssl req -new -key ca.key -out ca.csr
openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt

Hoàn thành các lệnh trên sẽ thu được public key tại /certtest/ca.crt và private key tại /certtest/ca.key

Lúc này mở /etc/httpd/conf.d/mvcblog.conf cập nhật thành

<VirtualHost *:*>
    RequestHeader set "X-Forwarded-Proto" expr="%{REQUEST_SCHEME}e"
</VirtualHost>

<VirtualHost *:80>
    ServerName mvcblog.vn
    ServerAlias *.mvcblog.vn

    # chuyển hướng http sang https
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]
</VirtualHost>

<VirtualHost *:443>
    ServerName mvcblog.vn
    ServerAlias *.mvcblog.vn

    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8090/
    ProxyPassReverse / http://127.0.0.1:8090/
    ErrorLog ${APACHE_LOG_DIR}helloapp-error.log
    CustomLog ${APACHE_LOG_DIR}helloapp-access.log common
    
    # Cấu hình HTTPS
    SSLEngine on
    # SSLProtocol all -SSLv2
    # SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:!RC4+RSA:+HIGH:+MEDIUM:!LOW:!RC4
    SSLCertificateFile /certtest/ca.crt
    SSLCertificateKeyFile /certtest/ca.key
</VirtualHost>>

Khởi động lại Apache, giờ đã có thể truy cập với giao thức https

Dùng nginx với Kestrel trên CENTOS chạy asp.net

Cài đặt nginx trên CentOS 7

Tạo file /etc/yum.repos.d/nginx.repo với nội dung

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=1

Thực hiện các lệnh:

yum install nginx
systemctl enable nginx
systemctl start nginx

Mở file cấu hình /etc/nginx/conf.d/default.conf sửa thành

server {
    listen        80;
    server_name   mvcblog.vn *.mvcblog.vn;
    location / {
        proxy_pass         http://localhost:8090;
        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;
    }
}

File cấu hình này thiết lập nginx lắng nghe ở cổng 80, cho các truy vấn đến từ tên miền mvcblog.vn, nó chuyể traffic đến ứng dụng ASP.NET ở cổng 8090

Cấu hình nginx sử dụng https cho ứng dụng asp.net core

Tạo ra file /etc/nginx/proxy.conf, biên tập nội dung như sau:

proxy_redirect          off;
proxy_set_header        Host $host;
proxy_set_header        X-Real-IP $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header        X-Forwarded-Proto $scheme;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_connect_timeout   90;
proxy_send_timeout      90;
proxy_read_timeout      90;
proxy_buffers           32 4k;

Mở file /etc/nginx/nginx.conf biên tập thành

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include        /etc/nginx/proxy.conf;
    limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
    server_tokens  off;

    sendfile on;
    keepalive_timeout   29; # Adjust to the lowest possible value that makes sense for your use case.
    client_body_timeout 10; client_header_timeout 10; send_timeout 10;

    upstream mvcblog {
        server localhost:8090;
    }

    server {
        listen     *:80;
        add_header Strict-Transport-Security max-age=15768000;
        return     301 https://$host$request_uri;
    }

    server {
        listen                    *:443 ssl;
        server_name               example.com;
        ssl_certificate           /certtest/ca.crt;
        ssl_certificate_key       /certtest/ca.key;
        ssl_protocols             TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers               "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_ecdh_curve            secp384r1;
        ssl_session_cache         shared:SSL:10m;
        ssl_session_tickets       off;
        ssl_stapling              on; #ensure your cert is capable
        ssl_stapling_verify       on; #ensure your cert is capable

        add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;

        #Redirects all traffic
        location / {
            proxy_pass http://mvcblog;
            limit_req  zone=one burst=10 nodelay;
        }
    }
}

Khởi động lại nginx sau khi cập nhật cấu hình


Đăng ký nhận bài viết mới