GoSuda

Esecuzione di un server Go in modo estensibile in dotnet aspire

By snowmerak
views ...

dotnet aspire?

dotnet aspire๋Š” ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ํ™˜๊ฒฝ์ด ๋Š˜์–ด๋‚จ์— ๋”ฐ๋ผ, ๊ฐœ๋ฐœ์ž๋“ค์˜ ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ๊ฐœ๋ฐœ ๋ฐ ๊ตฌ์„ฑ์„ ๋•๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์ง„ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์ด ๋„๊ตฌ๋Š” .NET ๊ฐœ๋ฐœ์ž๋“ค์ด ๋‹ท๋„ท ํ”„๋กœ์ ํŠธ ๋ฐ ๋‹ค์–‘ํ•œ ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ์ธํ”„๋ผ, ๊ทธ๋ฆฌ๊ณ  ๋‹ค๋ฅธ ์–ธ์–ด๋กœ ๋œ ์„œ๋น„์Šค๋‚˜ ์ปจํ…Œ์ด๋„ˆ ๋“ฑ์„ ์‰ฝ๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

๋‹น์—ฐํžˆ docker์—์„œ k8s๊นŒ์ง€ ์ถœ์‹œ ๋ฐ ์šด์˜๋˜๋ฉฐ, ๊ธฐ์กด์˜ ์˜จํ”„๋ ˆ๋ฏธ์Šค ํ™˜๊ฒฝ์—์„œ ์ƒ๋‹นํžˆ ๋งŽ์€ ๋ถ„์•ผ, ์‚ฐ์—…, ๊ฐœ๋ฐœ์ž๋“ค์ด ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ํ™˜๊ฒฝ์œผ๋กœ ์ด์ „ํ•˜๊ณ  ์žˆ๊ณ , ์ด์ „ํ•œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ์ด์ œ๋Š” ์„ฑ์ˆ™๋œ ๋ถ„์•ผ์ฃ . ๊ทธ๋ ‡๊ธฐ์— ํ˜ธ์ŠคํŠธ ์ด๋ฆ„, ํฌํŠธ ๊ตฌ์„ฑ, ๋ฐฉํ™”๋ฒฝ, ๋ฉ”ํŠธ๋ฆญ ๊ด€๋ฆฌ ๋“ฑ์— ๋Œ€ํ•ด ๊ธฐ์กด์˜ ๋ถˆํŽธํ•จ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•  ํ•„์š”๊ฐ€ ์—†์„ ๊ฑฐ๋ผ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‹น์žฅ ์œ„์˜ ์„ค๋ช…๋“ค๋กœ ๋ฏธ๋ฃจ์–ด๋ด๋„ dotnet aspire๊ฐ€ ๋ญ”์ง€ ๋„์ €ํžˆ ๊ฐ์ด ์•ˆ ์žกํž ๊ฒ๋‹ˆ๋‹ค. ์™œ๋ƒ๋ฉด, ์ด๊ฑด ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๋„ ์ •ํ™•ํ•œ ์ •์˜๋ฅผ ๋‚ด๋ฆฌ์ง„ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ €๋„ ๋ณ„๋‹ค๋ฅธ ์ •์˜๋ฅผ ๋‚ด๋ฆฌ์ง„ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ์ด ๊ธ€์—์„œ ์ œ๊ฐ€ ์ดํ•ดํ•œ dotnet aspire์˜ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ฏ€๋กœ, ์ฐธ๊ณ ํ•˜์—ฌ ๋ณธ์ธ๋งŒ์˜ ์œ„์น˜๋ฅผ ์ •ํ•˜์‹œ๋ฉด ๋  ๊ฒƒ๊ฐ™์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ

dotnet aspire ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

๋งŒ์•ฝ dotnet aspire ํ…œํ”Œ๋ฆฟ์ด ์—†๋‹ค๋ฉด, ํ…œํ”Œ๋ฆฟ๋ถ€ํ„ฐ ์„ค์น˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ํ…œํ”Œ๋ฆฟ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ .net์ด ์—†๋‹ค๋ฉด, ๊ทธ๊ฑด ๋ณธ์ธ์ด ์„ค์น˜ํ•ด์ฃผ์„ธ์š”.

1dotnet new install Aspire.ProjectTemplates

๊ทธ๋ฆฌ๊ณ  ์ ๋‹นํ•œ ํด๋”์—์„œ ์ƒˆ๋กœ์šด ์†”๋ฃจ์…˜์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

1dotnet new sln

๊ทธ ํ›„, ์†”๋ฃจ์…˜ ํด๋”์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ aspire-apphost ํ…œํ”Œ๋ฆฟ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

1dotnet new aspire-apphost -o AppHost

๊ทธ๋Ÿผ ์„ธํŒ…์„ ์œ„ํ•œ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋งŒ ์กด์žฌํ•˜๋Š” aspire-apphost ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

Valkey ์ถ”๊ฐ€

๊ทธ๋Ÿผ ๊ฐ„๋‹จํ•˜๊ฒŒ Valkey๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ฐ”๋กœ ์ถ”๊ฐ€ํ•˜๊ธฐ ์ „์—, dotnet aspire๋Š” community hosting์„ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ์„œ๋“œํŒŒํ‹ฐ ์†”๋ฃจ์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
๋‹น์—ฐํžˆ valkey๋„ ์ด๋Ÿฌํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ํ˜ธ์ŠคํŒ…์˜ ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‹ค์Œ nuget ํŒจํ‚ค์ง€๋ฅผ ํ†ตํ•ด ์‰ฝ๊ฒŒ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1dotnet add package Aspire.Hosting.Valkey

์™ธ์— ๋‹ค์–‘ํ•œ ํ†ตํ•ฉ๋œ ํ˜ธ์ŠคํŒ…์„ ์ œ๊ณตํ•˜๋‹ˆ, ์ด๊ณณ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค์‹œ valkey๋กœ ๋Œ์•„์™€์„œ AppHost ํ”„๋กœ์ ํŠธ์—์„œ Program.cs ํŒŒ์ผ์„ ์—ด์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

1var builder = DistributedApplication.CreateBuilder(args);
2
3var cache = builder.AddValkey("cache")
4    .WithDataVolume(isReadOnly: false)
5    .WithPersistence(interval: TimeSpan.FromMinutes(5),
6        keysChangedThreshold: 100);
7
8builder.Build().Run();

cache๋Š” valkey ์„œ๋น„์Šค๋ฅผ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋ฅผ ๊ฐ€์ง„ IResourceBuilder ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ž…๋‹ˆ๋‹ค.
WithDataVolume์€ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋ณผ๋ฅจ์„ ์ƒ์„ฑํ•˜๋ฉฐ, WithPersistence๋Š” ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์†์ ์œผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
์—ฌ๊ธฐ๊นŒ์ง€ ๋ณด๋ฉด docker-compose์˜ volumes์™€ ๋น„์Šทํ•œ ์—ญํ• ์„ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.
๋‹น์—ฐํ•˜๊ฒŒ๋„ ์ด๋Š” ์—ฌ๋Ÿฌ๋ถ„๋“ค๋„ ์–ด๋ ต์ง€ ์•Š๊ฒŒ ๋งŒ๋“œ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์ด ๊ธ€์˜ ๋ฒ”์œ„๋ฅผ ๋„˜์–ด๊ฐ€๋ฏ€๋กœ ์ง€๊ธˆ ์ด์•ผ๊ธฐํ•˜์ง€๋Š” ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

Go ์–ธ์–ด ์—์ฝ” ์„œ๋ฒ„ ์ƒ์„ฑ

๊ทธ๋Ÿผ ๊ฐ„๋‹จํ•œ Go ์–ธ์–ด ์„œ๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
์ผ๋‹จ ์†”๋ฃจ์…˜ ํด๋”์—์„œ go work init์„ ํ†ตํ•ด ์›Œํฌ์ŠคํŽ˜์ด์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
๋‹ท๋„ท ๊ฐœ๋ฐœ์ž์—๊ฒ Go ์›Œํฌ์ŠคํŽ˜์ด์Šค๋Š” ์†”๋ฃจ์…˜๊ณผ ์œ ์‚ฌํ•œ ๊ฑฐ๋ผ ๋ณด์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  EchoServer๋ผ๋Š” ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ , ์•ˆ์œผ๋กœ ์ด๋™ํ•œ ํ›„ go mod init EchoServer๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
์ด ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด Go ๋ชจ๋“ˆ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ์€ ๋‹ท๋„ท ๊ฐœ๋ฐœ์ž์—๊ฒŒ ํ”„๋กœ์ ํŠธ์™€ ์œ ์‚ฌํ•œ ๊ฒƒ์œผ๋กœ ์ธ์ง€ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  main.go ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

 1package main
 2
 3import (
 4	"log"
 5	"net/http"
 6	"os"
 7)
 8
 9func main() {
10	addr := os.Getenv("PORT")
11	log.Printf("Server started on %s", addr)
12
13	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
14		name := request.URL.Query().Get("name")
15		writer.Write([]byte("Hello, " + name))
16	})
17
18	http.ListenAndServe(":"+addr, nil)
19}

์ด ์„œ๋ฒ„๋Š” Aspire AppHost๊ฐ€ ์‹คํ–‰๋  ๋•Œ, listening์„ ํ•ด์•ผํ•  PORT ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ฃผ์ž…ํ•ด์ฃผ๋ฉด, ํ•ด๋‹น ํฌํŠธ๋ฅผ ์ฝ์–ด์„œ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
๊ฐ„๋‹จํ•˜๊ฒŒ name ์ฟผ๋ฆฌ๋ฅผ ๋ฐ›์•„์„œ Hello, {name}์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค.

์ด์ œ ์ด ์„œ๋ฒ„๋ฅผ dotnet aspire์— ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์—์ฝ” ์„œ๋ฒ„๋ฅผ aspire์— ์ถ”๊ฐ€ํ•˜๊ธฐ

๋‹ค์‹œ Valkey๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋˜ Aspire AppHost๋กœ ํ”„๋กœ์ ํŠธ๋กœ ์ด๋™ํ•ด์„œ Go ์–ธ์–ด๋ฅผ ์œ„ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ํ˜ธ์ŠคํŒ…์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

1dotnet add package CommunityToolkit.Aspire.Hosting.Golang

๊ทธ๋ฆฌ๊ณ  Program.cs ํŒŒ์ผ์„ ์—ด์–ด ๋‹ค์Œ ๊ตฌ๋ฌธ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

 1var builder = DistributedApplication.CreateBuilder(args);
 2
 3var cache = builder.AddValkey("cache")
 4    .WithDataVolume(isReadOnly: false)
 5    .WithPersistence(interval: TimeSpan.FromMinutes(5),
 6        keysChangedThreshold: 100);
 7
 8var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
 9    .WithHttpEndpoint(port: 3000, env: "PORT")
10    .WithExternalHttpEndpoints();
11
12builder.Build().Run();

์—ฌ๊ธฐ์„œ echoServer๋Š” Go ์–ธ์–ด ์„œ๋ฒ„๋ฅผ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋ฅผ ๊ฐ€์ง„ IResourceBuilder ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์ฒด์ž…๋‹ˆ๋‹ค.
๋ฐฉ๊ธˆ ์ถ”๊ฐ€ํ•œ AddGolangApp ๋ฉ”์„œ๋“œ๋Š” Go ์–ธ์–ด ์„œ๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•œ ์ปค์Šคํ…€ ํ˜ธ์ŠคํŠธ์˜ ํ™•์žฅ ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.
๊ณ ์ •์ ์œผ๋กœ 3000 ํฌํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, PORT ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ฃผ์ž…ํ•ด์ฃผ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋งˆ์ง€๋ง‰์œผ๋กœ WithExternalHttpEndpoints๋Š” ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด http://localhost:3000/?name=world๋กœ ์ ‘์†ํ•ด๋ณด์‹œ๋ฉด, Hello, world๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๊ฑธ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํ˜„์žฌ dotnet aspire์—๋Š” non-dotnet project์—๊ฒŒ ์ฃผ์–ด์ง€๋Š” ๋ฌด๊ฑฐ์šด ํŒจ๋„ํ‹ฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ...

ํ”„๋กœ์ ํŠธ ํ™•์žฅ

์ˆ˜ํ‰ ํ™•์žฅ์€ ๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ํ•ด?

ํ˜„์žฌ dotnet aspire๋Š” AddProject ๋ฉ”์„œ๋“œ๋กœ ์ถ”๊ฐ€๋œ ๋‹ท๋„ท ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•œ ๋นŒ๋”์—๋งŒ WithReplica ์˜ต์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ Go ์–ธ์–ด ํ˜ธ์ŠคํŠธ๋‚˜ AddContainer๊ฐ™์€ ์™ธ๋ถ€ ํ”„๋กœ์ ํŠธ์— ๋Œ€ํ•ด์„œ๋Š” ์ด ์˜ต์…˜์„ ์ œ๊ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ์— ๋ณ„๋„์˜ load balancer๋‚˜ reverse proxy๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์ด๋Ÿฌ๋ฉด ํ•ด๋‹น reverse proxy๊ฐ€ SPOF๊ฐ€ ๋  ์ˆ˜ ์žˆ๊ธฐ์—, reverse proxy๋Š” WithReplica ์˜ต์…˜์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด ํ•„์—ฐ์ ์œผ๋กœ reverse proxy๋Š” ๋‹ท๋„ท ํ”„๋กœ์ ํŠธ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์—ฌํƒœ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์— ๋Œ€ํ•ด์„œ nginx, trafik, ์ง์ ‘ ๊ตฌํ˜„ ๋“ฑ์˜ ๋ฐฉ๋ฒ•์„ ์จ์™”์ง€๋งŒ, ๋‹ท๋„ท ํ”„๋กœ์ ํŠธ๋ผ๋Š” ์ œํ•œ์ด ๊ฑธ๋ฆฌ๋ฉด ์ œ ์†์—์„œ๋Š” ๋‹น์žฅ ๋ฐฉ๋ฒ•์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๋‹ท๋„ท์œผ๋กœ ๊ตฌํ˜„๋œ reverse proxy๋ฅผ ์ฐพ์•„๋ณด์•˜๊ณ , ๋‹คํ–‰ํžˆ YARP๋ผ๋Š” ์„ ํƒ์ง€๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
YARP๋Š” ๋‹ท๋„ท์œผ๋กœ ๊ตฌํ˜„๋œ reverse proxy๋กœ, load balancer ์—ญํ• ๋„ ํ•  ์ˆ˜ ์žˆ๊ณ , ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ์— ์ข‹์€ ์„ ํƒ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ ์ด์ œ YARP๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

YARP๋กœ reverse proxy ๊ตฌ์„ฑ

๋จผ์ € YARP๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

1dotnet new web -n ReverseProxy

๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ์ ํŠธ๋กœ ์ด๋™ํ•ด์„œ YARP๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

1dotnet add package Yarp.ReverseProxy --version 2.2.0

์„ค์น˜๊ฐ€ ๋๋‚˜๋ฉด, Program.cs ํŒŒ์ผ์„ ์—ด์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

 1using Yarp.ReverseProxy.Configuration;
 2
 3var builder = WebApplication.CreateBuilder(args);
 4
 5var routes = new List<RouteConfig>();
 6var clusters = new List<ClusterConfig>();
 7
 8builder.Services.AddReverseProxy()
 9    .LoadFromMemory(routes, clusters);
10
11var app = builder.Build();
12
13app.MapReverseProxy();
14app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");

์ด ์ฝ”๋“œ๋Š” YARP๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ์ ์ธ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
routes๋Š” reverse proxy๊ฐ€ ์‚ฌ์šฉํ•  ๋ผ์šฐํŠธ ์ •๋ณด๋ฅผ, clusters๋Š” reverse proxy๊ฐ€ ์‚ฌ์šฉํ•  ํด๋Ÿฌ์Šคํ„ฐ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
์ด ์ •๋ณด๋“ค์€ LoadFromMemory ๋ฉ”์„œ๋“œ๋กœ reverse proxy์— ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.
๋งˆ์ง€๋ง‰์œผ๋กœ MapReverseProxy ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ reverse proxy๋ฅผ ๋งคํ•‘ํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์‹ค์‚ฌ์šฉ์„ ์œ„ํ•ด aspire apphost ํ”„๋กœ์ ํŠธ์—์„œ reverse proxy ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐธ์กฐ๋กœ ๋„ฃ์–ด์ฃผ๊ณ , Program.cs ํŒŒ์ผ์— ๋‹ค์Œ ๊ตฌ๋ฌธ์„ ์ถ”๊ฐ€ ๋ฐ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

1dotnet add reference ../ReverseProxy
1var echoServer = builder.AddGolangApp("echo-server", "../EchoServer")
2    .WithHttpEndpoint(env: "PORT");
3
4var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
5    .WithReference(echoServer)
6    .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
7    .WithExternalHttpEndpoints();

์ด์ œ reverse proxy๊ฐ€ echo server๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์™ธ๋ถ€์—์„œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์€ reverse proxy์—์„œ ๋ฐ›๊ณ  echo server๋กœ ๋„˜๊ฒจ์ฃผ๋Š” ๊ตฌ์กฐ๋กœ ๋ณ€๊ฒฝ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Reverse proxy ์ˆ˜์ •

์ผ๋‹จ์€ reverse proxy์— ํ• ๋‹น๋œ ํ”„๋กœ์ ํŠธ์˜ listening ์ฃผ์†Œ๋ฅผ ๋ณ€๊ฒฝํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
Properties/launchSettings.json ํŒŒ์ผ ๋‚ด๋ถ€์˜ applicationUrl์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  Program.cs ํŒŒ์ผ์„ ์—ด์–ด ์•„๋ž˜์™€ ๊ฐ™์ด ๋Œ€๋Œ€์ ์œผ๋กœ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

 1using Yarp.ReverseProxy.Configuration;
 2
 3var builder = WebApplication.CreateBuilder(args);
 4
 5var routes = new List<RouteConfig>
 6{
 7    new RouteConfig
 8    {
 9        ClusterId = "cluster-echo",
10        RouteId = "route-echo",
11        Match = new RouteMatch
12        {
13            Path = "/"
14        }
15    }
16};
17
18var echoServerAddr = Environment.GetEnvironmentVariable("services__echo-server__http__0") ?? "http://localhost:8080";
19
20var clusters = new List<ClusterConfig>
21{
22    new ClusterConfig
23    {
24        ClusterId = "cluster-echo",
25        Destinations = new Dictionary<string, DestinationConfig>
26        {
27            { "destination-echo", new DestinationConfig { Address = echoServerAddr } }
28        }
29    }
30};
31
32builder.Services.AddReverseProxy()
33    .LoadFromMemory(routes, clusters);
34
35var app = builder.Build();
36
37app.MapReverseProxy();
38app.Run(url: $"http://0.0.0.0:{Environment.GetEnvironmentVariable("PORT") ?? "5000"}");

๋จผ์ € routes์™€ clusters์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
๊ฐ๊ฐ echo-route์™€ echo-cluster๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ, echo server๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  echo server์˜ ์ฃผ์†Œ๋ฅผ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ๋ถ€ํ„ฐ ์ฝ์–ด์™€์„œ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ฃผ์†Œ์˜ ๊ทœ์น™์€ services__{service-name}__http__{index}์ž…๋‹ˆ๋‹ค.
echo server์˜ ๊ฒฝ์šฐ, ์„œ๋น„์Šค ์ด๋ฆ„์ด echo-server์ด๊ณ , ๋‹จ์ผ ์ธ์Šคํ„ด์Šค์ด๊ธฐ์— ์ธ๋ฑ์Šค๋กœ 0์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ asp .net core ์„œ๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด, WithReplica๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ธ๋ฑ์Šค๋ฅผ ์ฆ๊ฐ€์‹œ์ผœ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ๋Š” http://localhost:8080์€ ์•„๋ฌด๋Ÿฐ ๋œป์ด ์—†๋Š” ์“ฐ๋ ˆ๊ธฐ ๊ฐ’์ž…๋‹ˆ๋‹ค.

์ด์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , http://localhost:3000/?name=world๋กœ ์ ‘์†ํ•ด๋ณด์‹œ๋ฉด, ์—ฌ์ „ํžˆ Hello, world๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๊ฑธ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค.

ํ™•์žฅ ์•„์ด๋””์–ด

์ด์ œ dotnet aspire์— Go ์„œ๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , reverse proxy๋ฅผ ํ†ตํ•ด ์š”์ฒญ์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ์ด ๊ณผ์ •์„ programmaticํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ™•์žฅํ•  ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด, echo server์— ๋Œ€ํ•ด์„œ ์„œ๋น„์Šค ์ด๋ฆ„ ๋’ค์— ๋„˜๋ฒ„๋ง์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ , reverse proxy์— ๋Œ€ํ•œ ์„ค์ •์„ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

aspire apphost ํ”„๋กœ์ ํŠธ์˜ Program.cs ํŒŒ์ผ์— reverse proxy์™€ echo server๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

 1var reverseProxy = builder.AddProject<Projects.ReverseProxy>("gateway")
 2    .WithHttpEndpoint(port: 3000, env: "PORT", isProxied: true)
 3    .WithExternalHttpEndpoints();
 4
 5for (var i = 0; i < 8; i++)
 6{
 7    var echoServer = builder.AddGolangApp($"echo-server-{i}", "../EchoServer")
 8        .WithHttpEndpoint(env: "PORT");
 9    reverseProxy.WithReference(echoServer);
10}

๊ทธ๋ฆฌ๊ณ  reverse proxy ํ”„๋กœ์ ํŠธ์˜ Program.cs ํŒŒ์ผ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

 1var echoServerDestinations = new Dictionary<string, DestinationConfig>();
 2for (var i = 0; i < 8; i++)
 3{
 4    echoServerDestinations[$"destination-{i}"] = new DestinationConfig
 5    {
 6        Address = Environment.GetEnvironmentVariable($"services__echo-server-{i}__http__0") ?? "http://localhost:8080"
 7    };
 8}
 9
10var clusters = new List<ClusterConfig>
11{
12    new ClusterConfig
13    {
14        ClusterId = "cluster-echo",
15        Destinations = echoServerDestinations
16    }
17};

8๊ฐœ๋กœ ๋Š˜์–ด๊ฐ„ echo server ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด ๋ชฉ์ ์ง€ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
์ด์ œ reverse proxy๋Š” ๋Š˜์–ด๋‚œ echo server๋“ค์— ๋Œ€ํ•œ ๋ชฉ์ ์ง€ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ , ์š”์ฒญ์„ ์ „๋‹ฌํ•ด์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
๊ธฐ์กด์˜ http://localhost:3000/?name=world๋กœ ์ ‘์†ํ•ด๋ณด์‹œ๋ฉด, ์—ฌ์ „ํžˆ Hello, world๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๊ฑธ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค.

๋งˆ์น˜๋ฉฐ

์ด ๊ธ€์—์„œ๋Š” dotnet aspire์— Go ์„œ๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , reverse proxy๋ฅผ ํ†ตํ•ด ์š”์ฒญ์„ ์ „๋‹ฌํ•˜๋Š” ๊ณผ์ •์„ ์„ค๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค.
๋‹ค๋งŒ ํ™•์žฅ์— ๊ด€ํ•ด์„œ๋Š” ์•„์ง ๋ชจ๋‘ ์ž‘์„ฑํ•˜์ง„ ์•Š์•˜๊ณ , ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ํ†ตํ•ด ์ข€ ๋” programmaticํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์ œ๋ฅผ ๋ณ„๋„ ๋ ˆํฌ์— ์ž‘์„ฑํ•ด๋†“์•˜์Šต๋‹ˆ๋‹ค.
์ž์„ธํ•œ ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ๊ณผ ์ฝ”๋“œ๋Š” snowmerak/AspireStartPack์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

์ €๋Š” ๊ฐœ์ธ์ ์œผ๋กœ dotnet aspire๊ฐ€ docker compose์˜ ๋Œ€์•ˆ์œผ๋กœ์จ, ๊ทธ๋ฆฌ๊ณ  ํด๋ผ์šฐ๋“œ ๋ฐฐํฌ ํˆด๋กœ์จ ์ž์‹ ๋งŒ์˜ ๋กค์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๊ธฐ๋Œ€ํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฏธ docker compose๋‚˜ k8s manifest๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์—ฌ, ์ผ๋ฐ˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ธํ”„๋ผ ๋„๊ตฌ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ฑ์ด ๋” ์ข‹์•„์ง€์ง€ ์•Š์•˜๋‚˜ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.