I'm trying to use Ocelot (Api gateway) + consul + my web api (.Net 5) via HTTPS in docker;
ocelot - v17.0.0
consul - latest https://hub.docker.com/_/consul
my service - ASP.NET 5 Web Api
Trust HTTPS certificate from Windows Subsystem for Linux
source: https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-5.0&tabs=visual-studio#trust-https-certificate-from-windows-subsystem-for-linux
MY_SECRET_PROJECT_PATH\LicenseServiceWebApi> dotnet dev-certs https --clean
Cleaning HTTPS development certificates from the machine. A prompt might get displayed to confirm the removal of some of the certificates.
HTTPS development certificates successfully removed from the machine.
MY_SECRET_PROJECT_PATH\LicenseServiceWebApi> dotnet dev-certs https -ep $env:USERPROFILE\.aspnet\https\aspnetdev.pfx -p <водкабалалайка>
The HTTPS developer certificate was generated successfully.
MY_SECRET_PROJECT_PATH\LicenseServiceWebApi> dotnet dev-certs https --trust
Trusting the HTTPS development certificate was requested. A confirmation prompt will be displayed if the certificate was not previously trusted. Click yes on the prompt to trust the certificate.
A valid HTTPS certificate is already present.
My docker-compose:
version: '3'
services:
license-service-web-api-01:
image: license-service-web-api
container_name: license-service-01
build:
context: .
dockerfile: Services/LicenseServiceWebApi/Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:9001;http://+:9000
- ASPNETCORE_Kestrel__Certificates__Default__Password=${Kestrel_Certificate_Password}
- ASPNETCORE_Kestrel__Certificates__Default__Path=/root/.aspnet/https/aspnetdev.pfx
volumes:
- ${USERPROFILE}\.aspnet\https:/root/.aspnet/https/:ro
ports:
- 9000:9000
- 9001:9001
depends_on:
- consul
gateway:
image: gateway
container_name: gateway
build:
context: .
dockerfile: ApiGateway/gateway/Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:5001;http://+:5000
- ASPNETCORE_Kestrel__Certificates__Default__Password=${Kestrel_Certificate_Password}
- ASPNETCORE_Kestrel__Certificates__Default__Path=/root/.aspnet/https/aspnetdev.pfx
volumes:
- ${USERPROFILE}\.aspnet\https:/root/.aspnet/https/:ro
ports:
- 5001:5001
- 5000:5000
depends_on:
- consul
consul:
image: consul
container_name: consul
command: agent -server -ui -node=server-1 -bootstrap-expect=1 -client=0.0.0.0
environment:
- 'CONSUL_LOCAL_CONFIG= {"connect": {"enabled": true}}'
ports:
- 8500:8500
My configuration file "ocelot.json" (version without Consul)
{
"Routes": [
{
"SwaggerKey": "License Service",
"DownstreamPathTemplate": "/api/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "license-service-web-api-01",
"Port": 9000
}
],
"UpstreamHttpMethod": [ "Put", "Post", "GET" ],
"UpstreamPathTemplate": "/{everything}",
"LoadBalancerOptions": {
"Type": "LeastConnection"
},
"FileCacheOption": {
"TtlSeconds": 30
}
},
{
"DownstreamPathTemplate": "/swagger/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "license-service-web-api-01",
"Port": 9000
}
],
"UpstreamPathTemplate": "/swagger/{everything}",
"FileCacheOption": {
"TtlSeconds": 666
}
}
],
"SwaggerEndPoints": [
{
"Key": "License Service",
"Config": [
{
"Name": "License Service API",
"Version": "v1",
"Service": {
"Name": "License Service",
"Path": "/swagger/v1/swagger.json"
}
}
]
}
]
}
docker-compose ps
Name Command State Ports
----------------------------------------------------------------------------------------------------------------------------------------------------------
consul docker-entrypoint.sh agent ... Up 8300/tcp, 8301/tcp, 8301/udp, 8302/tcp, 8302/udp, 0.0.0.0:8500->8500/tcp, 8600/tcp, 8600/udp
gateway dotnet gateway.dll Up 443/tcp, 0.0.0.0:5000->5000/tcp, 0.0.0.0:5001->5001/tcp
license-service-01 dotnet LicenseServiceWebAp ... Up 443/tcp, 0.0.0.0:9000->9000/tcp, 0.0.0.0:9001->9001/tcp
CASE 1:
PS C:\Users\tim> curl http://localhost:9000/api/HealthCheck/GetMachineName
StatusCode : 200
StatusDescription : OK
Content : MachineName=2a429d520129
RawContent : HTTP/1.1 200 OK
PS C:\Users\tim> curl https://localhost:9001/api/HealthCheck/GetMachineName
StatusCode : 200
StatusDescription : OK
Content : MachineName=2a429d520129
RawContent : HTTP/1.1 200 OK
PS C:\Users\tim> curl http://localhost:5000/HealthCheck/GetMachineName
StatusCode : 200
StatusDescription : OK
Content : MachineName=2a429d520129
RawContent : HTTP/1.1 200 OK
PS C:\Users\tim> curl https://localhost:5001/HealthCheck/GetMachineName
StatusCode : 200
StatusDescription : OK
Content : MachineName=2a429d520129
RawContent : HTTP/1.1 200 OK
CASE 2:
I added an https redirect in my service
app.UseHttpsRedirection();
PS C:\Users\tim> curl http://localhost:9000/api/HealthCheck/GetMachineName
StatusCode : 200
StatusDescription : OK
Content : MachineName=345437c4c182
RawContent : HTTP/1.1 200 OK
PS C:\Users\tim> curl https://localhost:9001/api/HealthCheck/GetMachineName
StatusCode : 200
StatusDescription : OK
Content : MachineName=345437c4c182
RawContent : HTTP/1.1 200 OK
PS C:\Users\tim> curl http://localhost:5000/HealthCheck/GetMachineName
curl : Unable to resolve the remote name: 'license-service-web-api-01'
docker-compose logs ..
gateway | info: Ocelot.RateLimit.Middleware.ClientRateLimitMiddleware[0]
gateway | requestId: 0HM6VQAI1UTJU:00000002, previousRequestId: no previous request id, message: EndpointRateLimiting is not enabled for /api/{everything}
gateway | info: Ocelot.Authentication.Middleware.AuthenticationMiddleware[0]
gateway | requestId: 0HM6VQAI1UTJU:00000002, previousRequestId: no previous request id, message: No authentication needed for /HealthCheck/GetMachineName
gateway | info: Ocelot.Authorization.Middleware.AuthorizationMiddleware[0]
gateway | requestId: 0HM6VQAI1UTJU:00000002, previousRequestId: no previous request id, message: /api/{everything} route does not require user to be authorized
gateway | info: Ocelot.Requester.Middleware.HttpRequesterMiddleware[0]
gateway | requestId: 0HM6VQAI1UTJU:00000002, previousRequestId: no previous request id, message: 307 (Temporary Redirect) status code, request uri: http://license-service-web-api-01:9000/api/HealthCheck/GetMachineName
PS C:\Users\tim> curl https://localhost:5001/HealthCheck/GetMachineName
curl : Unable to resolve the remote name: 'license-service-web-api-01'
..The logs are the same
CASE 3
I'm changed configuration file ocelot.json on https and port 9001
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "license-service-web-api-01",
"Port": 9001
}
],
AND
removed line app.UseHttpsRedirection(); in my service
GET https://localhost:5001/WeatherForecast
502
225 ms
Warning: Unable to verify the first certificate
GET /WeatherForecast HTTP/1.1
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Postman-Token: 6cafa9e0-e195-4082-a7d4-daac4f58dff7
Host: localhost:5001
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.1 502 Bad Gateway
Date: Fri, 05 Mar 2021 14:50:01 GMT
Server: Kestrel
Content-Length: 0
gateway | info: Ocelot.RateLimit.Middleware.ClientRateLimitMiddleware[0]
gateway | requestId: 0HM6VSNQ8J5GI:00000002, previousRequestId: no previous request id, message: EndpointRateLimiting is not enabled for /api/{everything}
gateway | info: Ocelot.Authentication.Middleware.AuthenticationMiddleware[0]
gateway | requestId: 0HM6VSNQ8J5GI:00000002, previousRequestId: no previous request id, message: No authentication needed for /WeatherForecast
gateway | info: Ocelot.Authorization.Middleware.AuthorizationMiddleware[0]
gateway | requestId: 0HM6VSNQ8J5GI:00000002, previousRequestId: no previous request id, message: /api/{everything} route does not require user to be authorized
gateway | warn: Ocelot.Responder.Middleware.ResponderMiddleware[0]
gateway | requestId: 0HM6VSNQ8J5GI:00000002, previousRequestId: no previous request id, message: Error Code: ConnectionToDownstreamServiceError Message: Error connecting to downstream service, exception: System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
gateway | ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch, RemoteCertificateChainErrors
gateway | at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)
gateway | at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
gateway | at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
gateway | --- End of inner exception stack trace ---
gateway | at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
gateway | at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
gateway | at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
gateway | at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
gateway | at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
gateway | at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
gateway | at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
gateway | at Ocelot.Requester.HttpClientHttpRequester.GetResponse(HttpContext httpContext) errors found in ResponderMiddleware. Setting error response for request path:/WeatherForecast, request method: GET
I have questions, maybe someone can help me, please:
Why ocelot can't call my serive over https?
How i can https enabled for Consul inside docker - compose?
you have a ssl error last case. You may 2 options for this issue
best.option you creating your own certificate and then getting it trusted by your local or remote machine.
quick option you can add this line your ocelot.json
"DangerousAcceptAnyServerCertificateValidator": true
you should add networks tag on your docker-compose file.
like below:
version: '3'
networks:
dockerapi:
driver: bridge
services:
license-service-web-api-01:
image: license-service-web-api
container_name: license-service-01
build:
context: .
dockerfile: Services/LicenseServiceWebApi/Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:9001;http://+:9000
- ASPNETCORE_Kestrel__Certificates__Default__Password=${Kestrel_Certificate_Password}
- ASPNETCORE_Kestrel__Certificates__Default__Path=/root/.aspnet/https/aspnetdev.pfx
volumes:
- ${USERPROFILE}\.aspnet\https:/root/.aspnet/https/:ro
ports:
- 9000:9000
- 9001:9001
depends_on:
- consul
networks:
- dockerapi
gateway:
image: gateway
container_name: gateway
build:
context: .
dockerfile: ApiGateway/gateway/Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=https://+:5001;http://+:5000
- ASPNETCORE_Kestrel__Certificates__Default__Password=${Kestrel_Certificate_Password}
- ASPNETCORE_Kestrel__Certificates__Default__Path=/root/.aspnet/https/aspnetdev.pfx
volumes:
- ${USERPROFILE}\.aspnet\https:/root/.aspnet/https/:ro
ports:
- 5001:5001
- 5000:5000
depends_on:
- consul
networks:
- dockerapi
Related
This question already has an answer here:
Connecting to rabbitmq docker container from service in another container
(1 answer)
Closed last year.
I am currently stuck with this problem for about a week and really can't find an appropriate solution. The problem is that when I try to connect to dockerized RabbitMQ it gives me the same error every time:
wordofthedayapp-wordofthedayapp-1 | [40m[1m[33mwarn[39m[22m[49m: MassTransit[0]
wordofthedayapp-wordofthedayapp-1 | Connection Failed: rabbitmq://localhost/
wordofthedayapp-wordofthedayapp-1 | RabbitMQ.Client.Exceptions.BrokerUnreachableException: None of the specified endpoints were
reachable
wordofthedayapp-wordofthedayapp-1 | ---> System.AggregateException: One or more errors occurred. (Connection failed)
wordofthedayapp-wordofthedayapp-1 | ---> RabbitMQ.Client.Exceptions.ConnectFailureException: Connection failed
wordofthedayapp-wordofthedayapp-1 | ---> System.TimeoutException: The operation has timed out.
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.Impl.TaskExtensions.TimeoutAfter(Task task, TimeSpan timeout)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.Impl.SocketFrameHandler.ConnectOrFail(ITcpClient socket, AmqpTcpEndpo
int endpoint, TimeSpan timeout)
wordofthedayapp-wordofthedayapp-1 | --- End of inner exception stack trace ---
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.Impl.SocketFrameHandler.ConnectOrFail(ITcpClient socket, AmqpTcpEndpo
int endpoint, TimeSpan timeout)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.Impl.SocketFrameHandler.ConnectUsingAddressFamily(AmqpTcpEndpoint end
point, Func`2 socketFactory, TimeSpan timeout, AddressFamily family)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.Impl.SocketFrameHandler.ConnectUsingIPv4(AmqpTcpEndpoint endpoint, Fu
nc`2 socketFactory, TimeSpan timeout)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.Impl.SocketFrameHandler..ctor(AmqpTcpEndpoint endpoint, Func`2 socket
Factory, TimeSpan connectionTimeout, TimeSpan readTimeout, TimeSpan writeTimeout)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.Framing.Impl.IProtocolExtensions.CreateFrameHandler(IProtocol protoco
l, AmqpTcpEndpoint endpoint, Func`2 socketFactory, TimeSpan connectionTimeout, TimeSpan readTimeout, TimeSpan writeTimeout)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.ConnectionFactory.CreateFrameHandler(AmqpTcpEndpoint endpoint)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.EndpointResolverExtensions.SelectOne[T](IEndpointResolver resolver, F
unc`2 selector)
wordofthedayapp-wordofthedayapp-1 | --- End of inner exception stack trace ---
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.EndpointResolverExtensions.SelectOne[T](IEndpointResolver resolver, F
unc`2 selector)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.ConnectionFactory.CreateConnection(IEndpointResolver endpointResolver
, String clientProvidedName)
wordofthedayapp-wordofthedayapp-1 | --- End of inner exception stack trace ---
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.ConnectionFactory.CreateConnection(IEndpointResolver endpointResolver
, String clientProvidedName)
wordofthedayapp-wordofthedayapp-1 | at RabbitMQ.Client.ConnectionFactory.CreateConnection(IList`1 hostnames, String clientPr
ovidedName)
wordofthedayapp-wordofthedayapp-1 | at MassTransit.RabbitMqTransport.Integration.ConnectionContextFactory.CreateConnection(I
Supervisor supervisor)
Here you can find my docker-compose.yml:
version: '3.9'
services:
rabbitmq:
image: rabbitmq:3.9-management
hostname: rabbitmq
volumes:
- "~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/"
- "~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq"
ports:
- 5672:5672
- 15672:15672
expose:
- 5672
- 15672
environment:
- RABBITMQ_DEFAULT_USER=guest
- RABBITMQ_DEFAULT_PASS=guest
healthcheck:
test: [ "CMD", "rabbitmqctl", "status", "-f", "http://localhost:15672"]
interval: 5s
timeout: 20s
retries: 5
networks:
- app
ms-sql-server:
container_name: ms-sql-server
image: mcr.microsoft.com/mssql/server:2019-latest
user: root
volumes:
- "appdb:/var/opt/mssql/data"
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "Password123!"
MSSQL_PID: Express
ports:
- 1433:1433
healthcheck:
test: ["CMD" ,"ping", "-h", "localhost"]
timeout: 20s
retries: 10
networks:
- app
wordofthedayapp:
build:
dockerfile: WordOfTheDay.Api/Dockerfile
image: wordofthedayapp
environment:
DbServer: "ms-sql-server"
DbPort: "1433"
DbUser: "sa"
Password: "Password123!"
Database: "appdb"
ports:
- 5001:80
restart: on-failure
depends_on:
- rabbitmq
networks:
- app
volumes:
appdb:
networks:
app:
My appsettings string:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"WordContext": "Server=ms-sql-server;Database=master;User=sa;Password=Password123!;MultipleActiveResultSets=true;Integrated Security=false;TrustServerCertificate=true",
"RabbitMQHost": "amqp://elias:123456#localhost:5672"
}
}
This is how it works in the app using MassTransit:
public static void AddConfiguredMassTransit(this IServiceCollection services, string host)
{
services.AddMassTransit(Configuration =>
{
Configuration.UsingRabbitMq((context, config) =>
{
config.Host(host);
});
});
services.AddMassTransitHostedService();
}
services.AddConfiguredMassTransit(Configuration.GetConnectionString("RabbitMQHost"));
I hope at least anyone knows what is wrong with this code because I really tired trying to fix it and browsing internet for solution. Thank you in advance!
P.S. Important information! Everything works perfect when I test it locally without a docker, but when I try to dockerize the app this happens.
If you are testing locally, the host is likely localhost since Docker is exposing the port to the local machine. When running in a container, however, the virtual network should have a hostname of rabbitmq, which would need to be used instead of localhost when running inside a container on the same network.
Since the log shows:
Connection Failed: rabbitmq://localhost/
I'm guessing you aren't updating the host name when running inside a container.
You can determine if your application is running in a container easily:
bool IsRunningInContainer => bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), out var inDocker) && inDocker;
Then, in your configuration:
var host = IsRunningInContainer ? "rabbitmq" : "localhost";
I'm making an web service using docker-compose environment. But I face to the calling API issue. I'm using nextjs and here is my code.
import { GetServerSideProps } from 'next';
export default function Home() {
return (
...
);
}
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
fetch('http://localhost:7070/api/users') // <-- error occurred !
.then((r) => r.json())
.then((result) => console.log('######## shoot in getServerSideProps', result));
return {
props: {},
};
};
This is sample code. When I access to this page, the following error comes up
/app/node_modules/node-fetch/lib/index.js:1461
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
^
FetchError: request to http://localhost:7070/api/users failed, reason: connect ECONNREFUSED 127.0.0.1:7070
How can I call the fetch API?
My docker setting is following.
docker-compose
version: "3"
services:
servicebackend:
container_name: seiwhale-backend
build:
dockerfile: Dockerfile.dev
context: ./services/server
volumes:
- /app/node_modules
- ./services/server:/app
servicefrontend:
container_name: seiwhale-frontend
build:
dockerfile: Dockerfile.dev
context: ./services/webapp
volumes:
- /app/node_modules
- ./services/webapp:/app
serviceredis:
container_name: seiwhale-redis
image: "redis"
nginx:
container_name: seiwhale-nginx
restart: always
build:
dockerfile: Dockerfile
context: ./services/nginx
ports:
- "7070:80"
nginx
upstream upstreamfrontend {
server servicefrontend:8080;
}
upstream upstreambackend {
server servicebackend:3000;
}
server {
listen 80;
location / {
proxy_pass http://upstreamfrontend;
}
location /api {
proxy_pass http://upstreambackend;
}
location /sockjs-node {
proxy_pass http://upstreamfrontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
Additionally, when the error was occurred, the frontend container is automatically exited like following
seiwhale-frontend exited with code 1
How can I re-run the frontend container after exit?
2 mistakes:
You are missing the expose key from your docker-compose.yml config in which you define a list of ports that you want to expose to other services in the same docker network (or in this case, services defined in the same docker-compose file).
The frontend code running as a docker service and not from the host, so again you need to use expose and not port. (port is used for mapping ports from HOST:CONTAINER so applications outside the docker network can call it, for example, if something is listening on port 4200 of servicebackend and you define 8000:4200 then it is accessible on your host at localhost:8000.)
Please try again after making the following changes:
docker-compose.yml
version: "3"
services:
servicebackend:
container_name: seiwhale-backend
build:
dockerfile: Dockerfile.dev
context: ./services/server
volumes:
- /app/node_modules
- ./services/server:/app
expose:
- "3000"
servicefrontend:
container_name: seiwhale-frontend
build:
dockerfile: Dockerfile.dev
context: ./services/webapp
volumes:
- /app/node_modules
- ./services/webapp:/app
expose:
- "8080"
serviceredis:
container_name: seiwhale-redis
image: "redis"
nginx:
container_name: seiwhale-nginx
restart: always
build:
dockerfile: Dockerfile
context: ./services/nginx
ports:
- "7070:80"
expose:
- "80"
and in the frontend,
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
fetch('http://localhost:80/api/users') // <-- error occurred !
.then((r) => r.json())
.then((result) => console.log('######## shoot in getServerSideProps', result));
.error((err) => console.error(err))
return {
props: {},
};
};
I think I pretty much tried every possible way but no matter what I do, my NET 5.0 web app always connects to localhost:5000.
At startup I get this:
webbackend | warn: Microsoft.AspNetCore.Server.Kestrel[0]
webbackend | Unable to bind to http://localhost:5000 on the IPv6 loopback interface: 'Cannot assign requested address'.
webbackend | info: Microsoft.Hosting.Lifetime[0]
webbackend | Now listening on: http://localhost:5000
webbackend | info: Microsoft.Hosting.Lifetime[0]
webbackend | Application started. Press Ctrl+C to shut down.
webbackend | info: Microsoft.Hosting.Lifetime[0]
webbackend | Hosting environment: Production
webbackend | info: Microsoft.Hosting.Lifetime[0]
webbackend | Content root path: /app
Even though I have these in place:
Program.cs:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel()
.UseStartup<Startup>()
.UseUrls(http://0.0.0.0:80);
});
appsettings.json:
"commands": {
"web": "Microsoft.AspNet.Server.Kestrel --server.urls http://0.0.0.0:80"
}
Dockerfile:
ENTRYPOINT ["dotnet", "webbackend.dll", "--urls", "http://0.0.0.0:80"]
docker-compose.yml:
webbackend:
image: local_webbackend
container_name: webbackend
networks:
- my_network
environment:
ASPNETCORE_URLS: http://+:80
ports:
- "5001:80"
expose:
- "5432"
- "5001"
depends_on:
postgresdb:
condition: service_healthy
I really don't understand what is going on.
I just want this app to connect to localhost:80 inside its docker container. This port should then be connected to 5001 in the docker-compose network.
First you can change the UseUrls settings in Program.cs IHostBuilder
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseKestrel()
.UseStartup<Startup>()
.UseUrls("http://localhost:80/");
});
You can use the default Dockerfile created by Visual Studio or download it from Microsoft docs (you can specify all the parameters in docker-compose too, the important thing is to expose port 80, or other port that you have specified in the settings above). The settings are exposing the port 80 by default. In your docker-compose file you can set the settings like this:
webbackend:
image: local_webbackend
build: <path to generated or created Dockerfile>
container_name: webbackend
networks:
- my_network
environment:
ASPNETCORE_URLS: http://+:80
ports:
- "5001:80"
depends_on:
postgresdb:
condition: service_healthy
With this settings, apps inside the docker network, should connect to your app using URL like: webbacked:80. You can reach the app from your PC using: localhost:5001 and other devices from your LAN: :5001 (for example 192.168.1.100:5001).
I am trying to run an ASP.Net docker container on Kubernetes as a non-root user. I have this dockerfile:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 8443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["MyProgram.API/MyProgram.API.csproj", "MyProgram.API/"]
COPY ["MyProgram.Services/MyProgram.Services.csproj", "MyProgram.Services/"]
COPY ["MyProgram.Core/MyProgram.Core.csproj", "MyProgram.Core/"]
COPY ["MyProgram.Data/MyProgram.Data.csproj", "MyProgram.Data/"]
RUN dotnet restore "MyProgram.API/MyProgram.API.csproj"
COPY . .
WORKDIR "/src/MyProgram.API"
RUN dotnet build "MyProgram.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "MyProgram.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyProgram.API.dll"]
When I run it locally, I can go to https://localhost:8443 and use my app succesfully. When I deploy it to Kubernetes using this file:
apiVersion: apps/v1
kind: Deployment
# snip
spec:
securityContext:
fsGroup: 2000
runAsNonRoot: true
runAsUser: 1000
containers:
- name: myprogram
image: mycompany/myprogram:develop
imagePullPolicy: "Always"
env:
- name: "ASPNETCORE_ENVIRONMENT"
value: "Kubernetes"
ports:
- containerPort: 8443
name: "myprogram"
securityContext:
allowPrivilegeEscalation: false
imagePullSecrets:
- name: privatereposecret
---
apiVersion: v1
kind: Service
#snip
spec:
type: NodePort
ports:
- protocol: TCP
port: 8081
targetPort: 8443
nodePort: 31999
selector:
app: myprogram
My container won't start and gives these gives these log files:
[13:13:30 FTL] Unable to start Kestrel.
System.Net.Sockets.SocketException (13): Permission denied
at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, String callerName)
at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Bind(EndPoint localEP)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.<>c__DisplayClass21_0`1.<<StartAsync>g__OnBind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.AnyIPListenOptions.BindAsync(AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(IServerAddressesFeature addresses, KestrelServerOptions serverOptions, ILogger logger, Func`2 createBinding)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
[13:13:30 FTL] Application start-up failed
System.Net.Sockets.SocketException (13): Permission denied
at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, String callerName)
at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Bind(EndPoint localEP)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.<>c__DisplayClass21_0`1.<<StartAsync>g__OnBind|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.AnyIPListenOptions.BindAsync(AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(IServerAddressesFeature addresses, KestrelServerOptions serverOptions, ILogger logger, Func`2 createBinding)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
at MyProgram.API.Program.Main(String[] args) in /src/MyProgram.API/Program.cs:line 30
If I try the exact same deployment without a SecurityContext the container works perfectly. What's going wrong?
Kestrel is trying to bind to port 80 and/or port 443 because that's its default unless you tell it otherwise, and you can't do that unless priviledged.
You need to specify the ports, usually via environment variables, and expose them, e.g.
# Declare ports above 1024 as an unprivileged non-root user cannot bind to > 1024
ENV ASPNETCORE_URLS http://+:8000;https://+:8443
EXPOSE 8000
EXPOSE 8443
I use traefik as an ingress controller in AKS, I have a grpc service that run correctly locally, but I have some problem behind traefik.
When the GRPC server return an error, I receive it correctly, but when it send a normal response, I didn’t receive it:
grpcServer#grpc.test.com:443> client.Ping({}, metadata, pr)
EventEmitter {}
grpcServer#grpc.test.com:443>
Error: { Error: 2 UNKNOWN: No status received
at Object.exports.createStatusError (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/common.js:91:15)
at Object.onReceiveStatus (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:1204:28)
at InterceptingListener._callNext (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:568:42)
at InterceptingListener.onReceiveStatus (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:618:8)
at callback (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:845:24) code: 2, metadata: {}, details: 'No status received' }
vs
grpcServer#localhost:10000> client.Ping({}, metadata, pr)
EventEmitter {}
grpcServer#localhost:10000>
{
"response": "PONG"
}
Error:
grpcServer#grpc.test.com:443> client.Ping({}, pr)
EventEmitter {}
grpcServer#grpc.test.com:443>
Error: { Error: 16 UNAUTHENTICATED: incorrect serial number
at Object.exports.createStatusError (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/common.js:91:15)
at Object.onReceiveStatus (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:1204:28)
at InterceptingListener._callNext (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:568:42)
at InterceptingListener.onReceiveStatus (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:618:8)
at callback (/usr/local/lib/node_modules/grpcc/node_modules/grpc/src/client_interceptors.js:845:24)
code: 16,
metadata:
{ 'content-length': '0', date: 'Wed, 06 Feb 2019 21:32:38 GMT' },
details: 'incorrect serial number' }
The k8s service yml:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: velcloud-grpc
namespace: production
annotations:
kubernetes.io/ingress.class: traefik
ingress.kubernetes.io/protocol: h2c
traefik.protocol: h2c
spec:
rules:
- host: grpc.test.com
http:
paths:
- path: /
backend:
serviceName: velcloud-grpc
servicePort: grpc
Edit: More information
After some debugging, I inspected the received response:
{ client_close: true,
metadata: { date: [ 'Wed, 06 Feb 2019 23:48:06 GMT' ] },
read: <Buffer 0a 04 50 4f 4e 47>,
status:
{ code: 2,
details: 'No status received',
metadata: Metadata { _internal_repr: {} } } } { code: 2,
details: 'No status received'
}
The only problem here is the status, the read content the right response once deserialized: { response: 'PONG' }
I don't know why the status is set to 2 (UNKNOWN) and not 0 (OK).
In my Traefik configuration, I use the retry middleware([retry]) and Traefik v1.7.8 have a bug:
When the retry middleware has already sent the headers, use the original headers map to be able to send trailers.
https://github.com/containous/traefik/pull/4442
I manually build the v1.7 branch with this fix and everything works again!
Thanks a lot to Containous (Damien and Julien!) for helping to debug this issue!