Last active
November 9, 2020 07:29
-
-
Save guitarrapc/0626a43971274ea5b49d2ee9c7529252 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"Logging": { | |
"LogLevel": { | |
"Default": "Information", | |
"Microsoft": "Warning", | |
"Microsoft.Hosting.Lifetime": "Information" | |
} | |
}, | |
"Kestrel": { | |
"EndPoints": { | |
"Http": { | |
"Url": "http://0.0.0.0:80" | |
} | |
} | |
}, | |
"AllowedHosts": "*" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk.Web"> | |
<PropertyGroup> | |
<TargetFramework>netcoreapp3.1</TargetFramework> | |
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | |
</PropertyGroup> | |
<ItemGroup> | |
<Protobuf Include="Protos\aws.proto" GrpcServices="Server" /> | |
</ItemGroup> | |
<ItemGroup> | |
<PackageReference Include="Grpc.AspNetCore" Version="2.27.0" /> | |
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.33.1" /> | |
<PackageReference Include="Grpc.HealthCheck" Version="2.33.1" /> | |
<PackageReference Include="MagicOnion.Abstractions" Version="4.0.0-preview.1" /> | |
<PackageReference Include="MagicOnion.Server" Version="4.0.0-preview.1" /> | |
<PackageReference Include="ZLogger" Version="1.3.0" /> | |
</ItemGroup> | |
<ItemGroup> | |
<ProjectReference Include="..\EchoGrpcMagicOnion.Shared\EchoGrpcMagicOnion.Shared.csproj" /> | |
</ItemGroup> | |
<ItemGroup> | |
<None Update="appsettings.Development.json"> | |
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |
</None> | |
<None Update="appsettings.json"> | |
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |
</None> | |
</ItemGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using EchoGrpcMagicOnion.Shared; | |
using Grpc.Core; | |
using MagicOnion; | |
using MagicOnion.Server; | |
using MagicOnion.Server.Hubs; | |
using Microsoft.AspNetCore.Builder; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.AspNetCore.Routing; | |
using Microsoft.AspNetCore.Server.Kestrel.Core; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.Extensions.Logging; | |
using System; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using ZLogger; | |
namespace EchoGrpcMagicOnion | |
{ | |
class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
CreateHostBuilder(args).Build().Run(); | |
} | |
public static IHostBuilder CreateHostBuilder(string[] args) => | |
Host.CreateDefaultBuilder(args) | |
.ConfigureLogging((hostContext, logging) => | |
{ | |
logging.ClearProviders(); | |
logging.AddZLoggerConsole(); | |
}) | |
.ConfigureWebHostDefaults(webBuilder => | |
{ | |
webBuilder | |
.UseKestrel(options => | |
{ | |
// WORKAROUND: Accept HTTP/2 only to allow insecure HTTP/2 connections during development. | |
options.ConfigureEndpointDefaults(endpointOptions => | |
{ | |
endpointOptions.Protocols = HttpProtocols.Http2; | |
}); | |
}) | |
.UseStartup<Startup>(); | |
}); | |
} | |
public class Startup | |
{ | |
public Startup(IConfiguration configuration) | |
{ | |
Configuration = configuration; | |
} | |
public IConfiguration Configuration { get; } | |
// This method gets called by the runtime. Use this method to add services to the container. | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddGrpc(); // MagicOnion depends on ASP.NET Core gRPC service. | |
services.AddGrpcReflection(); | |
services.AddMagicOnion(options => | |
{ | |
options.GlobalStreamingHubFilters.Add<StreamingHeaderFilterAttribute>(); | |
}); | |
} | |
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. | |
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | |
{ | |
if (env.IsDevelopment()) | |
{ | |
app.UseDeveloperExceptionPage(); | |
} | |
else | |
{ | |
app.UseExceptionHandler("/Home/Error"); | |
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | |
app.UseHsts(); | |
} | |
//app.UseHttpsRedirection(); | |
app.UseRouting(); | |
app.UseEndpoints(endpoints => | |
{ | |
endpoints.MapMagicOnionService(); | |
endpoints.MapGrpcService<HealtchCheckService>(); | |
//endpoints.MapGrpcService<AlbService>(); | |
if (env.IsDevelopment()) | |
{ | |
endpoints.MapGrpcReflectionService(); | |
} | |
}); | |
} | |
} | |
/// <summary> | |
/// You don't need implement this service if status code 12 is enough. | |
/// `grpcurl -v -plaintext localhost:80 AWS.ALB/healthcheck` | |
/// </summary> | |
public class AlbService : ALB.ALBBase | |
{ | |
private readonly ILogger<AlbService> _logger; | |
public AlbService(ILogger<AlbService> logger) | |
{ | |
_logger = logger; | |
} | |
public override Task<Google.Protobuf.WellKnownTypes.Empty> healthcheck(Google.Protobuf.WellKnownTypes.Empty request, ServerCallContext context) | |
{ | |
_logger.LogInformation("healthy"); | |
return Task.FromResult(new Google.Protobuf.WellKnownTypes.Empty()); | |
} | |
} | |
/// <summary> | |
/// Implement https://github.com/grpc/grpc/tree/master/src/csharp/Grpc.HealthCheck | |
/// `grpcurl -plaintext 127.0.0.1:80 grpc.health.v1.Health.Check` | |
/// `grpc-health-probe -addr 127.0.0.1:80` | |
/// </summary> | |
public class HealtchCheckService : Grpc.Health.V1.Health.HealthBase | |
{ | |
public override Task<Grpc.Health.V1.HealthCheckResponse> Check(Grpc.Health.V1.HealthCheckRequest request, ServerCallContext context) | |
{ | |
return Task.FromResult(new Grpc.Health.V1.HealthCheckResponse | |
{ | |
Status = Grpc.Health.V1.HealthCheckResponse.Types.ServingStatus.Serving, | |
}); | |
} | |
} | |
public class HealthzService : ServiceBase<IHealthzService>, IHealthzService | |
{ | |
private readonly ILogger<HealthzService> _logger; | |
public HealthzService(ILogger<HealthzService> logger) | |
{ | |
_logger = logger; | |
} | |
public UnaryResult<int> Readiness() | |
{ | |
return UnaryResult<int>(0); | |
} | |
} | |
[FromTypeFilter(typeof(UnaryHeaderFilterAttribute))] | |
public class EchoService : ServiceBase<IEchoService>, IEchoService | |
{ | |
private readonly ILogger<EchoService> _logger; | |
public EchoService(ILogger<EchoService> logger) | |
{ | |
_logger = logger; | |
} | |
public async UnaryResult<string> EchoAsync(string request) | |
{ | |
_logger.LogDebug($"Handling Echo request '{request}' with context {Context}"); | |
var hostName = Environment.MachineName; | |
var metadata = new Metadata(); | |
metadata.Add("hostname", hostName); | |
await Context.CallContext.WriteResponseHeadersAsync(metadata); | |
return hostName; | |
} | |
} | |
public class MyHub : StreamingHubBase<IMyHub, IMyHubReceiver>, IMyHub | |
{ | |
private Player player; | |
private IGroup room; | |
private IInMemoryStorage<Player> storage; | |
private readonly ILogger<MyHub> _logger; | |
public MyHub(ILogger<MyHub> logger) | |
{ | |
_logger = logger; | |
} | |
public async Task<Player[]> JoinAsync(string roomName, string username) | |
{ | |
_logger.LogDebug($"Handling Stream Join request '{roomName}/{username}' with context {Context}"); | |
player = new Player | |
{ | |
Name = username | |
}; | |
(room, storage) = await Group.AddAsync(roomName, player); | |
Broadcast(room).OnJoin(player); | |
return storage.AllValues.ToArray(); | |
} | |
public async Task LeaveAsync() | |
{ | |
_logger.LogDebug($"Handling Stream Leave request '{room.GroupName}/{player.Name}' with context {Context}"); | |
await room.RemoveAsync(this.Context); | |
Broadcast(room).OnLeave(player); | |
} | |
public Task<string> EchoAsync(string message) | |
{ | |
_logger.LogDebug($"Handling Stream Echo request '{room.GroupName}/{player.Name}' with context {Context}"); | |
var hostName = Environment.MachineName; | |
Broadcast(room).OnEcho(hostName); | |
return Task.FromResult(hostName); | |
} | |
protected override async ValueTask OnConnecting() | |
{ | |
_logger.LogDebug($"OnConnecting {Context.ContextId}"); | |
var metadata = new Metadata(); | |
var headers = Context.CallContext.RequestHeaders; | |
if (!headers.Any(x => x.Key == "hostname")) | |
{ | |
var hostName = Environment.MachineName; | |
headers.Add("hostname", hostName); | |
await Context.CallContext.WriteResponseHeadersAsync(metadata); | |
} | |
} | |
protected override ValueTask OnDisconnected() | |
{ | |
_logger.LogDebug($"OnDisconnected {Context.ContextId}"); | |
return CompletedTask; | |
} | |
} | |
public class StreamingHeaderFilterAttribute : StreamingHubFilterAttribute | |
{ | |
private readonly ILogger<StreamingHeaderFilterAttribute> _logger; | |
public StreamingHeaderFilterAttribute(ILogger<StreamingHeaderFilterAttribute> logger) | |
{ | |
_logger = logger; | |
} | |
public override async ValueTask Invoke(StreamingHubContext context, Func<StreamingHubContext, ValueTask> next) | |
{ | |
_logger.LogInformation($"Request header contents of {context.Path}."); | |
_logger.LogInformation("{"); | |
foreach (var header in context.ServiceContext.CallContext.RequestHeaders) | |
{ | |
_logger.LogInformation($" {header.Key}: {header.Value}"); | |
} | |
_logger.LogInformation("}"); | |
await next(context); | |
} | |
} | |
public class UnaryHeaderFilterAttribute : MagicOnionFilterAttribute | |
{ | |
private readonly ILogger<UnaryHeaderFilterAttribute> _logger; | |
public UnaryHeaderFilterAttribute(ILogger<UnaryHeaderFilterAttribute> logger) | |
{ | |
_logger = logger; | |
} | |
public override async ValueTask Invoke(ServiceContext context, Func<ServiceContext, ValueTask> next) | |
{ | |
_logger.LogInformation($"Request header contents of {context.CallContext.Method}."); | |
_logger.LogInformation("{"); | |
foreach (var header in context.CallContext.RequestHeaders) | |
{ | |
_logger.LogInformation($" {header.Key}: {header.Value}"); | |
} | |
_logger.LogInformation("}"); | |
await next(context); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MY_DOMAIN=dummy.example.com | |
NAMESPACE=envoy-grpc | |
ACM=arn:aws:acm:us-west-2:xxxxx:certificate/xxxxxxx | |
TARGET_GROUP=arn:aws:elasticloadbalancing:us-west-2:xxxxx:targetgroup/xxxxxxx/xxxxxxxxxx | |
kubectl kustomize ./k8s_grpc_magiconion | | |
sed -e "s|gcr.io/GOOGLE_CLOUD_PROJECT|guitarrapc|g" | | |
sed -e "s|<namespace>|$NAMESPACE|g" | | |
sed -e "s|\.default|.$NAMESPACE|g" | | |
sed -e "s|<acm>|$ACM|g" | | |
sed -e "s|<domain>|$MY_DOMAIN|g" | | |
sed -e "s|<targetgroup>|$TARGET_GROUP|g" | |
# check | |
grpc-health-probe.exe -tls -addr $MY_DOMAIN:443 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
apiVersion: v1 | |
kind: Namespace | |
metadata: | |
name: <namespace> | |
--- | |
apiVersion: v1 | |
data: | |
envoy.yaml: | | |
admin: | |
access_log_path: /tmp/admin_access.log | |
address: | |
socket_address: | |
address: 127.0.0.1 | |
protocol: TCP | |
port_value: 9901 | |
static_resources: | |
listeners: | |
- name: listener_0 | |
address: | |
socket_address: | |
address: 0.0.0.0 | |
protocol: TCP | |
port_value: 10000 | |
filter_chains: | |
- filters: | |
- name: envoy.filters.network.http_connection_manager | |
typed_config: | |
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager | |
stat_prefix: ingress_http | |
access_log: | |
- name: envoy.file_access_log | |
typed_config: | |
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog | |
path: "/dev/stdout" | |
route_config: | |
name: local_route | |
virtual_hosts: | |
- name: http | |
domains: | |
- "*" | |
routes: | |
- match: | |
prefix: / | |
grpc: {} | |
route: | |
cluster: dynamic_forward_proxy_cluster | |
typed_per_filter_config: | |
envoy.filters.http.lua: | |
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute | |
name: forwardproxy.lua | |
envoy.filters.http.dynamic_forward_proxy: | |
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig | |
host_rewrite_header: x-host-port | |
metadata: | |
filter_metadata: | |
# expected header format 'x-host-port: 10-0-0-10'. otherwise forward to default_route. | |
envoy.filters.http.lua: | |
svc: ".echo-grpc" | |
port: 80 | |
header: x-host-port | |
default_route: "echo-grpc:80" | |
http_filters: | |
- name: envoy.filters.http.lua | |
typed_config: | |
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua | |
inline_code: | | |
# dummy | |
function envoy_on_request(request_handle) | |
return | |
end | |
source_codes: | |
forwardproxy.lua: | |
inline_string: | | |
function envoy_on_request(request_handle) | |
local conf_svc = request_handle:metadata():get("svc") | |
local conf_port = request_handle:metadata():get("port") | |
local conf_header_key = request_handle:metadata():get("header") | |
local conf_default_route = request_handle:metadata():get("default_route") | |
-- handle header | |
local xHostPort = conf_default_route | |
local original = "" | |
value = request_handle:headers():get(conf_header_key) | |
if value ~= nil then | |
request_handle:logDebug(string.format("Header %s: %s", conf_header_key, value)) | |
original = value | |
xHostPort = string.format("%s%s:%s", value, conf_svc, conf_port) | |
end | |
request_handle:headers():replace(conf_header_key, xHostPort) | |
-- check protocol | |
protocol = request_handle:streamInfo():protocol() | |
request_handle:logInfo(string.format("%s", protocol)) | |
-- debug log | |
request_handle:logInfo(string.format("%s, original=%s, after=%s", conf_header_key, original, xHostPort, changed)) | |
end | |
- name: envoy.filters.http.dynamic_forward_proxy | |
typed_config: | |
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig | |
dns_cache_config: | |
name: dynamic_forward_proxy_cache_config | |
dns_lookup_family: V4_ONLY | |
- name: envoy.filters.http.router | |
clusters: | |
- name: dynamic_forward_proxy_cluster | |
connect_timeout: 1s | |
lb_policy: CLUSTER_PROVIDED | |
http2_protocol_options: {} | |
ignore_health_on_host_removal: true | |
cluster_type: | |
name: envoy.clusters.dynamic_forward_proxy | |
typed_config: | |
"@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig | |
dns_cache_config: | |
name: dynamic_forward_proxy_cache_config | |
dns_lookup_family: V4_ONLY | |
kind: ConfigMap | |
metadata: | |
name: envoy-conf-579bdt5554 | |
namespace: <namespace> | |
--- | |
apiVersion: v1 | |
kind: Service | |
metadata: | |
name: echo-grpc | |
namespace: <namespace> | |
spec: | |
clusterIP: None | |
ports: | |
- name: http2-service | |
port: 80 | |
protocol: TCP | |
selector: | |
app: echo-grpc | |
type: ClusterIP | |
--- | |
apiVersion: v1 | |
kind: Service | |
metadata: | |
name: envoy-svc | |
namespace: <namespace> | |
spec: | |
ports: | |
- name: http | |
port: 10000 | |
protocol: TCP | |
targetPort: http | |
selector: | |
app: envoy | |
type: NodePort | |
--- | |
apiVersion: apps/v1 | |
kind: Deployment | |
metadata: | |
name: echo-grpc | |
namespace: <namespace> | |
spec: | |
replicas: 3 | |
selector: | |
matchLabels: | |
app: echo-grpc | |
template: | |
metadata: | |
labels: | |
app: echo-grpc | |
spec: | |
containers: | |
- image: guitarrapc/echo-magiconion:4.0.0-preview-1 | |
imagePullPolicy: Always | |
name: echo-grpc | |
ports: | |
- containerPort: 80 | |
readinessProbe: | |
exec: | |
command: | |
- /bin/grpc_health_probe | |
- -addr=127.0.0.1:80 | |
initialDelaySeconds: 5 | |
--- | |
apiVersion: apps/v1 | |
kind: Deployment | |
metadata: | |
name: envoy | |
namespace: <namespace> | |
spec: | |
replicas: 2 | |
selector: | |
matchLabels: | |
app: envoy | |
template: | |
metadata: | |
labels: | |
app: envoy | |
spec: | |
containers: | |
- image: envoyproxy/envoy-alpine:v1.16.0 | |
lifecycle: | |
preStop: | |
exec: | |
command: | |
- /bin/sh | |
- -c | |
- wget -qO- --post-data='' http://127.0.0.1:8001/healthcheck/fail && | |
sleep 10 | |
livenessProbe: | |
initialDelaySeconds: 15 | |
periodSeconds: 20 | |
tcpSocket: | |
port: 10000 | |
name: envoy | |
ports: | |
- containerPort: 10000 | |
name: http | |
readinessProbe: | |
initialDelaySeconds: 3 | |
periodSeconds: 10 | |
tcpSocket: | |
port: 10000 | |
volumeMounts: | |
- mountPath: /etc/envoy | |
name: config | |
volumes: | |
- configMap: | |
name: envoy-conf-579bdt5554 | |
name: config | |
--- | |
apiVersion: elbv2.k8s.aws/v1beta1 | |
kind: TargetGroupBinding | |
metadata: | |
name: envoy-grpc | |
namespace: <namespace> | |
spec: | |
networking: | |
ingress: | |
- from: | |
- ipBlock: | |
cidr: 0.0.0.0/0 | |
ports: | |
- port: 10000 | |
protocol: TCP | |
serviceRef: | |
name: envoy-svc | |
port: 10000 | |
targetGroupARN: <targetgroup> | |
targetType: ip | |
--- | |
apiVersion: extensions/v1beta1 | |
kind: Ingress | |
metadata: | |
annotations: | |
alb.ingress.kubernetes.io/actions.forward-single-tg: | | |
{"type":"forward","targetGroupARN": "<targetgroup>","serviceName":"envoy-svc","servicePort":10000} | |
alb.ingress.kubernetes.io/certificate-arn: <acm> | |
alb.ingress.kubernetes.io/load-balancer-attributes: routing.http2.enabled=true, | |
idle_timeout.timeout_seconds=60 | |
alb.ingress.kubernetes.io/scheme: internet-facing | |
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30 | |
alb.ingress.kubernetes.io/target-type: ip | |
external-dns.alpha.kubernetes.io/hostname: <domain> | |
kubernetes.io/ingress.class: alb | |
name: envoy-ingress | |
namespace: <namespace> | |
spec: | |
rules: | |
- http: | |
paths: | |
- backend: | |
serviceName: forward-single-tg | |
servicePort: use-annotation | |
path: /* |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment